Skip to content
Snippets Groups Projects
Select Git revision
  • master
1 result

apa2

Blame
  • apa2 7.20 KiB
    #!/usr/bin/python3
    """
    Anders Python Archiver
    
    Copyright (C) 2004-2019 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("""
    
    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, types.ModuleType(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')
                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 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
        importer = Importer(code)
        sys.path_hooks.append(importer)
        import types
        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)
    """, file=apa)
        if apa:
            apa.close()
            try:
                os.chmod(options.output, 0o755)
            except:
                pass