diff --git a/apa b/apa new file mode 100755 index 0000000000000000000000000000000000000000..f9fcfa3a1741491807ceee1baa8c290989c7501b --- /dev/null +++ b/apa @@ -0,0 +1,251 @@ +#!/usr/bin/python3 +""" +Anders Python Archiver + +Copyright (C) 2004-2023 Anders Blomdell <anders.blomdell@control.lth.se> + + 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. +""" +from __future__ import print_function + +import optparse +import os +import re +import string +import sys + +def getModuleName(s): + m = re.match("^(.*)/__init__\.py$", s) + if m: + return m.group(1).replace("/", ".") + m = re.match("^(.*)\.py$", s) + if m: + return m.group(1).replace("/", ".") + return None + +def emitCode(f, module, filename, code): + print("EMIT:", module, file=sys.stderr) + print('"%s" : ( "%s", """%s""")' % (quote(module), + quote(filename), + quote(code)), file=f) + +def quote(s): + return s.replace('\\', '\\\\').replace('"', '\\"') + +if __name__ == '__main__': + usage = "%prog [options] <main module> <additional modules>*" + optParser = optparse.OptionParser(usage=usage) + optParser.add_option('-d','--documentation', + action='store_true', + help='Show documentation') + optParser.add_option('--interpreter', + default=None, + action='store', + help='Interpreter to use') + 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 = open(options.output, 'w') + else: + apa = None + + # Emit code + interpreter = code[0][2].splitlines()[0] + if options.interpreter != None: + print("#!%s" % options.interpreter, file=apa) + elif interpreter.find("python") >= 0: + # Take interpreter version from main file + print(interpreter, file=apa) + else: + # Default interpreter + print("Interpreter defaulting to '#!/usr/bin/python3'", file=sys.stderr) + print("#!/usr/bin/python3", file=apa) + print("\"\"\"", file=apa) + print("Executable archive of '%s'" % code[0][0], file=apa) + print("\nThe following modules are archived:", file=apa) + print(" %-20s %s" % ('__main__', code[0][1]), file=apa) + for (n, f, c) in code[1:]: + print(" %-20s %s" % (n, f), file=apa) + print(""" +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 <archive name> + <archive name>.extract(<optional destination dir>) +\"\"\" +""", file=apa) + print("code = {", file=apa) + (n, f, c) = code[0] + emitCode(apa, '__main__', f, c) + for (n, f, c) in code[1:]: + print(",", file=apa) + emitCode(apa, n, f, c) + print("}", file=apa) + print(""" + +import sys +import os +import importlib.abc +import importlib.util + +class Finder(importlib.abc.MetaPathFinder): + + def __init__(self, loader, code): + self.loader = loader + self.code = code + pass + + def find_spec(self, fullname, path=None, target=None): + if fullname in self.code: + return importlib.util.spec_from_loader(fullname, self.loader) + return None + + pass + +class Loader(importlib.abc.Loader): + + def __init__(self, code): + self.code = code; + self._path = "APA[%s]" % os.path.realpath(sys.argv[0]) + pass + + def create_module(self, spec): + return None + + def exec_module(self, module): + filename,src = self.code[module.__name__] + mod = sys.modules.setdefault(module.__name__, module) + mod.__file__ = filename + mod.__loader__ = self + mod.__path__ = [ self._path ] + global BAD + BAD = None + try: + code = compile(src, "%s/%s" % (self._path, filename), 'exec') + 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] + (name, extension) = os.path.splitext(os.path.basename(filename)) + return name == '__init__' + + 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 + + pass + +def extract(dest='.'): + \"\"\"Extracts archived files in dest directory ('.' is default)\"\"\" + import os + import os.path + for (m, (f, c)) in list(code.items()): + 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 + loader = Loader(code) + finder = Finder(loader, code) + sys.meta_path.append(finder) + try: + # This will start actual execution + spec = finder.find_spec("__main__") + m = importlib.util.module_from_spec(spec) + loader.exec_module(m) + except SystemExit as e: + raise e + except: + if BAD: + import traceback + traceback.print_exception(*BAD) + pass + sys.exit(1) +""", file=apa) + if apa: + apa.close() + try: + os.chmod(options.output, 0o755) + except: + pass