#!/usr/bin/python """ Anders Python Archiver Copyright (C) 2004-2009 Anders Blomdell A small utility program to join a number of python modules into a single executable python program. http://www.control.lth.se/user/andersb/software/apa 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """ import optparse import os import re import string import sys def getModuleName(s): m = re.match("^(.*)/__init__\.py$", s) if m: return string.replace(m.group(1), "/", ".") m = re.match("^(.*)\.py$", s) if m: return string.replace(m.group(1), "/", ".") return None def emitCode(f, module, filename, code): print >> sys.stderr, "EMIT:", module print >> f, '"%s" : ( "%s", """%s""")' % (quote(module), quote(filename), quote(code)) def quote(s): return string.replace(string.replace(s, '\\', '\\\\'), '"', '\\"') if __name__ == '__main__': usage = "%prog [options]
*" optParser = optparse.OptionParser(usage=usage) optParser.add_option('-d','--documentation', action='store_true', help='Show documentation') optParser.add_option('-o','--output', action='store', help='Name of archive') optParser.add_option('-s','--strip', action='append', default=[], help='Strip this part from filenames') (options, args) = optParser.parse_args(sys.argv[1:]) if options.documentation: print __doc__ sys.exit(0) # Read all python files code = [] processed = set() for filename in args: archivename = filename for strip in options.strip: m = re.match("^%s/*(.*)" % strip, filename) if m: archivename = m.group(1) break if archivename in processed: continue processed.add(archivename) name = getModuleName(archivename) if name: f = open(filename) code.append((name, archivename, f.read())) f.close() else: print "Illegal filename '%s'" % filename sys.exit(1) if options.output: apa = file(options.output, 'w') else: apa = None # Emit code interpreter = code[0][2].splitlines()[0] if interpreter.find("python") >= 0: # Take interpreter version from main file print >> apa, interpreter else: # Default interpreter print >> sys.stderr, "Interpreter defaulting to '#!/usr/bin/python'" print >> apa, "#!/usr/bin/python" print >> apa, "\"\"\"" print >> apa, "Executable archive of '%s'" % code[0][0] print >> apa, "\nThe following modules are archived:" print >> apa, " %-20s %s" % ('__main__', code[0][1]) for (n, f, c) in code[1:]: print >> apa, " %-20s %s" % (n, f) print >> apa, """ Archive created by 'apa' (http://www.control.lth.se/user/andersb/software/apa) To extract as individual files: * Add a .py suffix to the archive (if needed) * Start a python interpreter * Run the following commands: import .extract() \"\"\" """ print >> apa, "code = {" (n, f, c) = code[0] emitCode(apa, '__main__', f, c) for (n, f, c) in code[1:]: print >> apa, "," emitCode(apa, n, f, c) print >> apa, "}" print >> apa, """ class Importer: \"\"\"Loads modules from local code dictionary \"\"\" def __init__(self, code): self.code = code; self.path = "<%s>" % str(self.__class__) if not self.path in sys.path: sys.path.insert(0, self.path) def __call__(self, path): if path == self.path: return self return None def find_module(self, fullname): try: self.code[fullname] return self except: return None def load_module(self, fullname): mod = sys.modules.setdefault(fullname, imp.new_module(fullname)) (filename, src) = self.code[fullname] mod.__file__ = filename mod.__loader__ = self mod.__path__ = [ self.path ] global BAD BAD = None try: code = compile(src, "%s/%s" % (self.path, filename), 'exec') if sys.version_info < (3,): exec("exec code in mod.__dict__") else: exec(code, mod.__dict__) except SystemExit as e: # Silently propagate exit raise e except Exception as e: # Silently propagate other exceptions as well BAD = sys.exc_info() raise e return mod def is_package(self, fullname): (filename, src) = self.code[fullname] return True def get_code(self, fullname): (filename, src) = self.code[fullname] code = compile(src, "%s/%s" % (self.path, filename), 'exec') return code def get_source(self, fullname): (filename, src) = self.code[fullname] return src def get_filename(self, fullname): (filename, src) = self.code[fullname] return filename def extract(dest='.'): \"\"\"Extracts archived files in dest directory ('.' is default)\"\"\" import os import os.path for (m, (f, c)) in code.iteritems(): filepath = '%s/%s' % (dest, f) dirpath = os.path.dirname(filepath) if os.path.exists(filepath): raise Exception('File already exists: %s' % filepath) if not os.path.exists(dirpath): os.makedirs(dirpath) f = file(filepath, 'w') f.write(c) f.close() os.chmod(filepath, 0o755) if __name__ == '__main__': import sys importer = Importer(code) sys.path_hooks.append(importer) import imp try: # This will start actual execution importer.load_module("__main__") except SystemExit as e: raise e except: if BAD: import traceback traceback.print_exception(*BAD) pass sys.exit(1) """ if apa: apa.close() try: os.chmod(options.output, 0755) except: pass