""" This script processes the output from the C preprocessor and extracts all qstr. Each qstr is transformed into a qstr definition of the form 'Q(...)'. This script works with Python 2.6, 2.7, 3.3 and 3.4. """ import re import argparse import os # Python 2/3 compatibility: # - iterating through bytes is different # - codepoint2name lives in a different module import platform if platform.python_version_tuple()[0] == '2': bytes_cons = lambda val, enc=None: bytearray(val) from htmlentitydefs import name2codepoint elif platform.python_version_tuple()[0] == '3': bytes_cons = bytes from html.entities import name2codepoint # end compatibility code # Blacklist of qstrings that are specially handled in further # processing and should be ignored QSTRING_BLACK_LIST = set(['NULL', 'number_of']) # add some custom names to map characters that aren't in HTML name2codepoint['hyphen'] = ord('-') name2codepoint['space'] = ord(' ') name2codepoint['squot'] = ord('\'') name2codepoint['comma'] = ord(',') name2codepoint['dot'] = ord('.') name2codepoint['colon'] = ord(':') name2codepoint['semicolon'] = ord(';') name2codepoint['slash'] = ord('/') name2codepoint['percent'] = ord('%') name2codepoint['hash'] = ord('#') name2codepoint['paren_open'] = ord('(') name2codepoint['paren_close'] = ord(')') name2codepoint['bracket_open'] = ord('[') name2codepoint['bracket_close'] = ord(']') name2codepoint['brace_open'] = ord('{') name2codepoint['brace_close'] = ord('}') name2codepoint['star'] = ord('*') name2codepoint['bang'] = ord('!') name2codepoint['backslash'] = ord('\\') name2codepoint['plus'] = ord('+') name2codepoint['dollar'] = ord('$') name2codepoint['equals'] = ord('=') name2codepoint['question'] = ord('?') name2codepoint['at_sign'] = ord('@') name2codepoint['caret'] = ord('^') name2codepoint['pipe'] = ord('|') name2codepoint['tilde'] = ord('~') def write_out(fname, output): if output: for m, r in [("/", "__"), ("\\", "__"), (":", "@"), ("..", "@@")]: fname = fname.replace(m, r) with open(args.output_dir + "/" + fname + ".qstr", "w") as f: f.write("\n".join(output) + "\n") def qstr_unescape(qstr): for name in name2codepoint: if "__" + name + "__" in qstr: continue if "_" + name + "_" in qstr: qstr = qstr.replace("_" + name + "_", str(unichr(name2codepoint[name]))) return qstr def process_file(f): output = [] last_fname = None for line in f: # match gcc-like output (# n "file") and msvc-like output (#line n "file") if line and (line[0:2] == "# " or line[0:5] == "#line"): m = re.match(r"#[line]*\s\d+\s\"([^\"]+)\"", line) assert m is not None fname = m.group(1) if not fname.endswith(".c"): continue if fname != last_fname: write_out(last_fname, output) output = [] last_fname = fname continue for match in re.findall(r'MP_QSTR_[_a-zA-Z0-9]+', line): name = match.replace('MP_QSTR_', '') if name not in QSTRING_BLACK_LIST: output.append('Q(' + qstr_unescape(name) + ')') write_out(last_fname, output) return "" def cat_together(): import glob import hashlib hasher = hashlib.md5() all_lines = [] outf = open(args.output_dir + "/out", "wb") for fname in glob.glob(args.output_dir + "/*.qstr"): with open(fname, "rb") as f: lines = f.readlines() all_lines += lines all_lines.sort() all_lines = b"\n".join(all_lines) outf.write(all_lines) outf.close() hasher.update(all_lines) new_hash = hasher.hexdigest() #print(new_hash) old_hash = None try: with open(args.output_file + ".hash") as f: old_hash = f.read() except IOError: pass if old_hash != new_hash: print("QSTR updated") try: # rename below might fail if file exists os.remove(args.output_file) except: pass os.rename(args.output_dir + "/out", args.output_file) with open(args.output_file + ".hash", "w") as f: f.write(new_hash) else: print("QSTR not updated") if __name__ == "__main__": parser = argparse.ArgumentParser(description='Generates qstr definitions from a specified source') parser.add_argument('command', help='Command (split/cat)') parser.add_argument('input_filename', help='Name of the input file (when not specified, the script reads standard input)') parser.add_argument('output_dir', help='Output directory to store individual qstr files') parser.add_argument('output_file', help='Name of the output file with collected qstrs') args = parser.parse_args() try: os.makedirs(args.output_dir) except OSError: pass if args.command == "split": with open(args.input_filename) as infile: process_file(infile) if args.command == "cat": cat_together()