#! /usr/bin/env python # -*- coding: utf-8 -*- python_command='/usr/bin/env python' encoding='utf-8' # Copyright © 2000, 2006, 2007 Alberto González Palomo # Author: Alberto González Palomo # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # LANG=en import sys import string import re import os import getopt def display_help(): print r''' Usage: pythonpp [options] [file]... Pre-processes a file and expands directives. -h, --help Display this message. -s, --source Outputs the produced python program, instead of executing it. This adds '.py' to the output file name. -d, --debug Sets debug mode. Each appearance increments debug level by one. E.g.: -ddd sets debug level to 3. Level 1: Shows some intermediate results. Level 2: Copies the produced python program to the file 'pythonpp.debug' before executing it. -l, --literal Set default quoting as literal. (default) -i, --interpolated Set default quoting as interpolated. -e, --execute=code Execute 'code' as python code before processing each file. File processing takes place in the same scope as this code. Lines can be separated with \n, but this is the only escape sequence converted. I.e., \t is not converted to a tab character. -c, --command=cmd The python executable location. --encoding=charset Charset encoding of the template files. Default is utf-8. -a, --affix=affix (Pre|Suf)fix to remove from input file name to form the output file name. It's a Perl-style regular expression. The default is '^template\.'. -C, --directory=dir Change to directory "dir". Like in "make", several occurrences of this option concatenate their effects: "-C / -C etc" is equivalent to "-C /etc", while "-C etc -C /" is equivalent to "-C /". -o, --output=path Output file or directory name. If 'path' has a '/' at the end, it's prepended to the normal output file names. In this case, an input file name is needed to generate the output file name which gets appended to 'path'. If 'path' is '-', output is sent to stdout. If no output file name is given, it's computed from the input file name, by removing the affix, except when input is stdin, in which case output is stdout. The "quote" is the first caracter in a line (perhaps preceded by a hash '#' symbol which is ignored if followed by a valid "quote" directive): '' Literal quoting. Outputs line as is. "" Interpolated quoting. Substitutes every ${expression} by the expression's value. It can't contain newlines. ! Execute the following python code line, or a special function. The first character after the '!' triggers some special functions: '<': evaluates the rest of the line as a python expression to get a file name, switch input to that file, and continue when finished. (like the C directive #include) There can be no spaces between the '!' and the '<', since this directive does not generate any python code of its own and thus we can make this restriction to avoid false positives. ':': the rest of the line is evaluated as an integer, and the extra indentation for produced lines is set according to it. An unsigned number, like "1", sets the indentation to that value (always using spaces), and a signed value like "+1" or "-1" increments or decrements the indentation by that amount. By default it indents using spaces, but if a "t" is put after the number, the characters inserted are tabulators. Each tab counts as 1 character. Thus, "!:+3t" indents with 3 tabs, and "!:-3" returns to the previous indentation, exactly like "!:-3t". The space between the '!' and the ':' sets the indentation for the generated python code. If the character after the '!' is anything else, the line is evaluated as python code. `` Back quote. Executes this line as a shell command, and outputs it's stdout. If the first character in a line isn't a valid "quote" symbol, it's processed using the default quoting. If a line produces an empty output, the newline character is suppressed except for lines without quote directive. Shell quotes execute one line at a time. Code quotes join all the lines before executing them as a program. There can be spaces between quote pairs. This sets the indentation for the generated python write() statement, for integration with code directives. Report bugs to matmota@matracas.org Last modified 2005-10-01 16:47. ''' def debug_write(message_line): sys.stderr.write(message_line + '\n') def main(): global top_process_level global line_default global code_to_execute global python_command global encoding line_default = line_literal code_to_execute = '' if not globals().has_key("python_command") or not python_command: raise Exception("Variable not defined: python_command") if not globals().has_key("encoding") or not encoding: raise Exception("Variable not defined: encoding") global output_indent_string output_indent_string = '' global pending_content pending_content = '' global source_file_name global source_file_line_number source_file_name = "" source_file_line_number = 0 of = '' output_source = 0 debug = 0 template_file_name_affix = '^template\.' try: opts, args = getopt.getopt(sys.argv[1:], 'hsdlie:c:a:C:o:', ('help', 'source', 'debug', 'literal', 'interpolated', 'execute=', 'command=', 'encoding=', 'affix=', 'directory=', 'output=')) except getopt.error, problem: print 'Command line option problem: ', problem, '\n' display_help() return(1) for o, a in opts: if (o == '-s')|(o == '--source'): output_source = 1 if (o == '-d')|(o == '--debug'): debug = debug + 1 if (o == '-l')|(o == '--literal'): line_default = line_literal if (o == '-i')|(o == '--interpolated'): line_default = line_interpolated if (o == '-e')|(o == '--execute'): code_to_execute = a if (o == '-c')|(o == '--command'): python_command = a if (o == '--encoding'): encoding = a if (o == '-a')|(o == '--affix'): template_file_name_affix = a if (o == '-C')|(o == '--directory'): os.chdir(a) if (o == '-o')|(o == '--output'): of = a if (o == '-h')|(o == '--help'): display_help() return(0) re_affix = re.compile(template_file_name_affix) if not args: args = ['-'] for file in args: top_process_level = 1 file_name = os.path.basename(file) file_dir = os.path.dirname(file) if (of == '-'): output_file = '' elif (of != '') and (of[-1:] != '/'): output_file = of else: if file == '-': output_file = '' elif of[-1:] != '/': output_file = os.path.join(file_dir, re.sub(re_affix, '', file_name) ) else: output_file = re.sub(re_affix, '', file_name) output_file = of + output_file if os.path.basename(output_file) == file_name: sys.stderr.write( 'Error: File name "' + file + '" doesn\'t match affix pattern "' + str(template_file_name_affix) + '". Not processed.\n') return(1) if output_source: pipe_command = '' if output_file != '': output_file = output_file + '.py' else: pipe_command = python_command if (output_file != '') & (pipe_command != ''): pipe_command = pipe_command + ' >' + output_file if pipe_command != '': if debug > 1: pipe_command = 'tee -a /tmp/pythonpp.debug | ' + pipe_command output = os.popen(pipe_command, 'w') else: output = open(output_file, 'w') try: if file == '-': source_file_name = "" process(sys.stdin, output, '') else: source_file_name = file f = open(file, 'r') process(f, output, os.path.dirname(file)) f.close() except: debug_write('Error: ' + str(sys.exc_info()[0]) + ": " + str(sys.exc_info()[1])); debug_write('file = \'' + source_file_name + '\'') debug_write('line = ' + str(source_file_line_number)) debug_write('container file = \'' + file + '\'') debug_write('pipe_command = \'' + pipe_command + '\'') debug_write('output_file = \'' + output_file + '\'') output.close if output_source & (output_file != ''): os.chmod(output_file, 0750) def interpolate_expression(matchobj): if not matchobj.group(1): return '$' value = string.replace(matchobj.group(1), '\\"', '"') return('"+str(' + value + ')+"') def line_literal(indent, content, output): content = string.replace(content, '\\', '\\\\') content = 'output_indent_string + "' + string.replace(content, '"', '\\"') + '\\n"' output.write(indent + 'out.write(' + content + ')\n') expression = re.compile('\${([^}\n]*)}') def line_interpolated(indent, content, output): content = string.replace(content, '\\', '\\\\') content = string.replace(content, '"', '\\"') content = re.sub(expression, interpolate_expression, content) if len(content) <= 2 and line_has_directive: return output.write(indent + 'out.write(output_indent_string + "' + content + '\\n")\n') def line_code(indent, content, output): global pending_content output.write(indent + content + '\n') if content[:4] == 'def ' and content[-1:] == ':': pending_content = '!global output_indent_string\n' def line_include(indent, content, output, input_file_dirname): global source_file_name global source_file_line_number try: file_name = eval(content[1:]) except: debug_write('Error: ' + str(sys.exc_info()[0]) + ": " + str(sys.exc_info()[1]) + ",\n" + " when evaluating '" + content[1:] + "'\n" + ' for file inclusion.'); debug_write('file = \'' + source_file_name + '\'') return if input_file_dirname: file_name = input_file_dirname + '/' + file_name container_source_file_name = source_file_name container_source_file_line_number = source_file_line_number f = open(file_name, 'r') process(f, output, os.path.dirname(file_name)) f.close() source_file_name = container_source_file_name source_file_line_number = container_source_file_line_number def line_indent(indent, content, output, input_file_dirname): global output_indent_string basic_indent_unit = " " if "t" == content[-1:]: basic_indent_unit = "\t" content = content[:-1] if "+" == content[1:2]: output_indent_string = output_indent_string + basic_indent_unit * eval(content[1:]) elif "-" == content[1:2]: output_indent_string = output_indent_string[0:eval(content[1:])] else: output_indent_string = basic_indent_unit * eval(content[1:]) output.write(indent + 'output_indent_string = ' + repr(output_indent_string) + '\n') def line_pipe(indent, content, output): content = string.replace(content, '\\', '\\\\') content = '"' + string.replace(string.strip(content), '"', '\\"') + '"' content = re.sub(expression, interpolate_expression, content) if not content[1:-1] and line_has_directive: return output.write(indent + 'pipe = os.popen(' + content + ')\n') output.write(indent + 'cmd_output = pipe.read()\n') #output.write(indent + 'if cmd_output:\n') output.write(indent + 'out.write(cmd_output)\n') #output.write(indent + 'pipe.close()\n') def output_pending(indent, output): global pending_content for content_line in pending_content.rstrip().split('\n'): if content_line[0] == "'": line_literal(indent, content_line[1:], output) elif content_line[0] == '"': line_interpolated(indent, content_line[1:], output) elif content_line[0] == '!': line_code(indent, content_line[1:], output) elif content_line[0] == '`': line_pipe(indent, content_line[1:], output) else: line_default(indent, content_line, output) pending_content = '' re_directive = re.compile(r'#?([\'"!`])([ \t]*)([\'"`]?)([^\n\r]*)') def process(fpi, output, input_file_dirname): global line_has_directive global re_directive global top_process_level global pending_content global source_file_line_number source_file_line_number = 0 if top_process_level: output.write('#! ' + python_command + '\n') output.write('# -*- coding: ' + encoding.lower() + ' -*-\n') if (code_to_execute != ''): output.write(string.replace(code_to_execute, '\\n', '\n') + '\n') output.write('import sys\n') output.write('import os\n') output.write('out = sys.stdout\n') output.write('output_indent_string = ""\n') top_process_level = 0 while 1: line = fpi.readline() if not line: break ++source_file_line_number matchobj = re_directive.match(line) if matchobj: line_has_directive = 1 directive = matchobj.group(1) indent = matchobj.group(2) content = matchobj.group(4) if (len(matchobj.group(3)) > 0 and directive[-1:] != matchobj.group(3)): content = matchobj.group(2) + matchobj.group(3) + content if pending_content: output_pending(indent, output) if directive == "'": line_literal (indent, content, output) continue elif directive == '"': line_interpolated (indent, content, output) continue elif directive == '!': if indent + content[:1] == '<': line_include(indent, content, output, input_file_dirname) elif content[:1] == ':': line_indent(indent, content, output, input_file_dirname) else: line_code (indent, content, output) continue elif directive == '`': line_pipe (indent, content, output) continue else: debug_write('Error: illegal quote "' + directive + '" in pythonpp/process()\n') else: line_has_directive = 0 if line[-1:] == '\n': line = line[:-1] line_default ('', line, output) #import profile #profile.run('main()') # Uncomment the main() invocation to use this file as a standalone command. #main()