| #!/usr/bin/env python |
| # |
| # |
| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| # |
| # |
| # |
| # transform_sql.py -- create a header file with the appropriate SQL variables |
| # from an SQL file |
| # |
| |
| |
| import os |
| import re |
| import sys |
| |
| |
| DEFINE_END = ' ""\n\n' |
| |
| |
| def usage_and_exit(msg): |
| if msg: |
| sys.stderr.write('%s\n\n' % msg) |
| sys.stderr.write( |
| 'USAGE: %s SQLITE_FILE [OUTPUT_FILE]\n' |
| ' stdout will be used if OUTPUT_FILE is not provided.\n' |
| % os.path.basename(sys.argv[0])) |
| sys.stderr.flush() |
| sys.exit(1) |
| |
| |
| class Processor(object): |
| re_comments = re.compile(r'/\*.*?\*/', re.MULTILINE|re.DOTALL) |
| |
| # a few SQL comments that act as directives for this transform system |
| re_format = re.compile('-- *format: *([0-9]+)') |
| re_statement = re.compile('-- *STMT_([A-Z_0-9]+)') |
| re_include = re.compile('-- *include: *([-a-z]+)') |
| re_define = re.compile('-- *define: *([A-Z_0-9]+)') |
| |
| def _sub_format(self, match): |
| vsn = match.group(1) |
| |
| self.close_define() |
| self.output.write('#define %s_%s \\\n' % (self.var_name, match.group(1))) |
| self.var_printed = True |
| |
| def _sub_statement(self, match): |
| name = match.group(1) |
| |
| self.close_define() |
| self.output.write('#define STMT_%s %d\n' % (match.group(1), |
| self.stmt_count)) |
| self.output.write('#define STMT_%d \\\n' % (self.stmt_count,)) |
| self.var_printed = True |
| |
| self.stmt_count += 1 |
| |
| def _sub_include(self, match): |
| filepath = os.path.join(self.dirpath, match.group(1) + '.sql') |
| |
| self.close_define() |
| self.process_file(open(filepath).read()) |
| |
| def _sub_define(self, match): |
| define = match.group(1) |
| |
| self.output.write(' APR_STRINGIFY(%s) \\\n' % define) |
| |
| def __init__(self, dirpath, output, var_name): |
| self.dirpath = dirpath |
| self.output = output |
| self.var_name = var_name |
| |
| self.stmt_count = 0 |
| self.var_printed = False |
| |
| self._directives = { |
| self.re_format : self._sub_format, |
| self.re_statement : self._sub_statement, |
| self.re_include : self._sub_include, |
| self.re_define : self._sub_define, |
| } |
| |
| def process_file(self, input): |
| input = self.re_comments.sub('', input) |
| |
| for line in input.split('\n'): |
| line = line.replace('"', '\\"') |
| line = re.sub(r'IS_STRICT_DESCENDANT_OF[(]([A-Za-z_.]+), ([?][0-9]+)[)]', |
| r"((\1) > (\2) || '/' AND (\1) < (\2) || '0') ", |
| line) |
| |
| if line.strip(): |
| handled = False |
| |
| for regex, handler in self._directives.iteritems(): |
| match = regex.match(line) |
| if match: |
| handler(match) |
| handled = True |
| break |
| |
| # we've handed the line, so skip it |
| if handled: |
| continue |
| |
| if not self.var_printed: |
| self.output.write('#define %s \\\n' % self.var_name) |
| self.var_printed = True |
| |
| # got something besides whitespace. write it out. include some whitespace |
| # to separate the SQL commands. and a backslash to continue the string |
| # onto the next line. |
| self.output.write(' "%s " \\\n' % line.rstrip()) |
| |
| # previous line had a continuation. end the madness. |
| self.close_define() |
| |
| def close_define(self): |
| if self.var_printed: |
| self.output.write(DEFINE_END) |
| self.var_printed = False |
| |
| |
| def main(input_filepath, output): |
| filename = os.path.basename(input_filepath) |
| input = open(input_filepath, 'r').read() |
| |
| var_name = re.sub('[-.]', '_', filename).upper() |
| |
| output.write( |
| '/* This file is automatically generated from %s.\n' |
| ' * Do not edit this file -- edit the source and rerun gen-make.py */\n' |
| '\n' |
| % (filename,)) |
| |
| proc = Processor(os.path.dirname(input_filepath), output, var_name) |
| proc.process_file(input) |
| |
| ### the STMT_%d naming precludes *multiple* transform_sql headers from |
| ### being used within the same .c file. for now, that's more than fine. |
| ### in the future, we can always add a var_name discriminator or use |
| ### the statement name itself (which should hopefully be unique across |
| ### all names in use; or can easily be made so) |
| if proc.stmt_count > 0: |
| output.write( |
| '#define %s_DECLARE_STATEMENTS(varname) \\\n' % (var_name,) |
| + ' static const char * const varname[] = { \\\n' |
| + ', \\\n'.join(' STMT_%d' % (i,) for i in range(proc.stmt_count)) |
| + ', \\\n NULL \\\n }\n') |
| |
| |
| if __name__ == '__main__': |
| if len(sys.argv) < 2 or len(sys.argv) > 3: |
| usage_and_exit('Incorrect number of arguments') |
| |
| # Note: we could use stdin, but then we'd have no var_name |
| input_filepath = sys.argv[1] |
| |
| if len(sys.argv) > 2: |
| output_file = open(sys.argv[2], 'w') |
| else: |
| output_file = sys.stdout |
| |
| main(input_filepath, output_file) |