apa 6.74 KB
Newer Older
Anders Blomdell's avatar
Anders Blomdell committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
#!/usr/bin/python
"""
Anders Python Archiver

Copyright (C) 2004 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/~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, name, filename, code):
    print >> sys.stderr, "EMIT:", name
    print >> f, '"%s" : ( "%s", "%s", """%s""")' % (quote(module),
                                              quote(name),
                                              quote(filename),
                                              quote(code))
    
def quote(s):
    return string.replace(string.replace(s, '\\', '\\\\'), '"', '\\"')

        
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('-o','--output',
                         action='store', 
                         help='Name of archive')
    (options, args) = optParser.parse_args(sys.argv[1:])

    if options.documentation:
        print __doc__
        sys.exit(0)

    # Read all python files
    code = []
    for filename in args:
        name = getModuleName(filename)
        if name:
            f = open(filename)
            code.append((name, filename, 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/~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>)
\"\"\"
"""
    print >> apa, "code = {"
    (n, f, c) = code[0]
    emitCode(apa, '__apa__main__', '__main__', f, c)
    for (n, f, c) in code[1:]:
        print >> apa, ","
        emitCode(apa, n, n, f, c)
    print >> apa, "}"
    print >> apa, """
import exceptions
import ihooks
import imp
import os
import os.path
import sys
import traceback

class StringImporter(ihooks.ModuleImporter, object):
    \"\"\"Loads modules from local code dictionary
    \"\"\"
    def __init__(self, code_dict):
        super(StringImporter, self).__init__()
        self.code_dict = code_dict;
        self.loaded = {}
        self.result = None
        
    def import_it(self, partname, fqname, parent, force_load=0):
        try:
            # First shot, return part from parent module
            return parent.__dict__[partname]
        except (KeyError, AttributeError):
            pass

        try:
            # Second shot, load it from already loaded code
            return self.loaded[fqname]
        except (KeyError, AttributeError):
            pass

        try:
            # Third shot, load it from code_dictionary
            (name, filename, code) = self.code_dict[fqname]
            module = self.loader.hooks.add_module(fqname)
            self.loaded[fqname] = module
            module.__name__ = name
            module.__file__ = filename
            try:
                comp = compile(code, filename, 'exec')
                exec comp in module.__dict__
            except exceptions.SystemExit, e:
                # Silently propagate exit
                raise e
            except Exception, e:
                # print module.__file__
                # print "StringLoading '%s' failed: %s [%s]" % (fqname,
                # e, e.__class__)
                traceback.print_exc()
                raise exceptions.SystemExit(1)
            
            if parent != None:
                parent.__dict__[partname] = module
            return module
        except (KeyError, AttributeError), e:
            pass
        
        # Fourth shot, default loader
        return ihooks.ModuleImporter.import_it(self, partname, fqname, parent, force_load)

    def reload(self, module):
        for (n, m) in self.loaded.iteritems():
            if m is module:
                (filename, code) = self.code_dict[n]
                exec code in module.__dict__
                return
        return super(StringImporter, self).reload(module)

def extract(dest='.'):
    \"\"\"Extracts archived files in dest directory ('.' is default)\"\"\"
    for (m, (n, 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, 0755)
    
if __name__ == '__main__':
    importer = StringImporter(code)
    importer.install()
    # This will start actual execution
    try:
        import __apa__main__
    except exceptions.SystemExit, e:
        raise e
    except:
        sys.exit(1)
"""
    if apa:
        apa.close()
        os.chmod(options.output, 0755)