Another batch of updates...
[python/dscho.git] / Mac / scripts / BuildApplet.py
blob8da28d96453f688965d8d139680bf388b61180a7
1 """Create an applet from a Python script.
3 This puts up a dialog asking for a Python source file ('TEXT').
4 The output is a file with the same name but its ".py" suffix dropped.
5 It is created by copying an applet template and then adding a 'PYC '
6 resource named __main__ containing the compiled, marshalled script.
7 """
9 DEBUG=0
11 import sys
12 sys.stdout = sys.stderr
14 import string
15 import os
16 import marshal
17 import imp
18 import macfs
19 import MACFS
20 import MacOS
21 from Res import *
23 # .pyc file (and 'PYC ' resource magic number)
24 MAGIC = imp.get_magic()
26 # Template file (searched on sys.path)
27 TEMPLATE = "PythonApplet"
29 # Specification of our resource
30 RESTYPE = 'PYC '
31 RESNAME = '__main__'
33 # A resource with this name sets the "owner" (creator) of the destination
34 # XXXX Should look for id=0
35 OWNERNAME = "owner resource"
37 # OpenResFile mode parameters
38 READ = 1
39 WRITE = 2
41 def findtemplate():
42 """Locate the applet template along sys.path"""
43 for p in sys.path:
44 template = os.path.join(p, TEMPLATE)
45 try:
46 template, d1, d2 = macfs.ResolveAliasFile(template)
47 break
48 except (macfs.error, ValueError):
49 continue
50 else:
51 die("Template %s not found on sys.path" % `TEMPLATE`)
52 return
53 template = template.as_pathname()
54 return template
56 def main():
57 global DEBUG
58 DEBUG=1
60 # Find the template
61 # (there's no point in proceeding if we can't find it)
63 template = findtemplate()
64 if DEBUG:
65 print 'Using template', template
67 # Ask for source text if not specified in sys.argv[1:]
69 if not sys.argv[1:]:
70 srcfss, ok = macfs.PromptGetFile('Select Python source file:', 'TEXT')
71 if not ok:
72 return
73 filename = srcfss.as_pathname()
74 tp, tf = os.path.split(filename)
75 if tf[-3:] == '.py':
76 tf = tf[:-3]
77 else:
78 tf = tf + '.applet'
79 dstfss, ok = macfs.StandardPutFile('Save application as:', tf)
80 if not ok: return
81 process(template, filename, dstfss.as_pathname())
82 else:
84 # Loop over all files to be processed
85 for filename in sys.argv[1:]:
86 process(template, filename, '')
88 def process(template, filename, output):
90 if DEBUG:
91 print "Processing", `filename`, "..."
93 # Read the source and compile it
94 # (there's no point overwriting the destination if it has a syntax error)
96 fp = open(filename)
97 text = fp.read()
98 fp.close()
99 try:
100 code = compile(text, filename, "exec")
101 except (SyntaxError, EOFError):
102 die("Syntax error in script %s" % `filename`)
103 return
105 # Set the destination file name
107 if string.lower(filename[-3:]) == ".py":
108 destname = filename[:-3]
109 rsrcname = destname + '.rsrc'
110 else:
111 destname = filename + ".applet"
112 rsrcname = filename + '.rsrc'
114 if output:
115 destname = output
117 # Try removing the output file
118 try:
119 os.unlink(output)
120 except os.error:
121 pass
124 # Create FSSpecs for the various files
126 template_fss = macfs.FSSpec(template)
127 template_fss, d1, d2 = macfs.ResolveAliasFile(template_fss)
128 dest_fss = macfs.FSSpec(destname)
130 # Copy data (not resources, yet) from the template
132 tmpl = open(template, "rb")
133 dest = open(destname, "wb")
134 data = tmpl.read()
135 if data:
136 dest.write(data)
137 dest.close()
138 tmpl.close()
140 # Open the output resource fork
142 try:
143 output = FSpOpenResFile(dest_fss, WRITE)
144 except MacOS.Error:
145 if DEBUG:
146 print "Creating resource fork..."
147 CreateResFile(destname)
148 output = FSpOpenResFile(dest_fss, WRITE)
150 # Copy the resources from the target specific resource template, if any
151 typesfound, ownertype = [], None
152 try:
153 input = FSpOpenResFile(rsrcname, READ)
154 except (MacOS.Error, ValueError):
155 pass
156 else:
157 typesfound, ownertype = copyres(input, output, [], 0)
158 CloseResFile(input)
160 # Check which resource-types we should not copy from the template
161 skiptypes = []
162 if 'SIZE' in typesfound: skiptypes.append('SIZE')
163 if 'BNDL' in typesfound: skiptypes = skiptypes + ['BNDL', 'FREF', 'icl4',
164 'icl8', 'ics4', 'ics8', 'ICN#', 'ics#']
165 skipowner = (ownertype <> None)
167 # Copy the resources from the template
169 input = FSpOpenResFile(template_fss, READ)
170 dummy, tmplowner = copyres(input, output, skiptypes, skipowner)
171 if ownertype == None:
172 ownertype = tmplowner
173 CloseResFile(input)
174 if ownertype == None:
175 die("No owner resource found in either resource file or template")
177 # Now set the creator, type and bundle bit of the destination
178 dest_finfo = dest_fss.GetFInfo()
179 dest_finfo.Creator = ownertype
180 dest_finfo.Type = 'APPL'
181 dest_finfo.Flags = dest_finfo.Flags | MACFS.kHasBundle
182 dest_finfo.Flags = dest_finfo.Flags & ~MACFS.kHasBeenInited
183 dest_fss.SetFInfo(dest_finfo)
185 # Make sure we're manipulating the output resource file now
187 UseResFile(output)
189 # Delete any existing 'PYC ' resource named __main__
191 try:
192 res = Get1NamedResource(RESTYPE, RESNAME)
193 res.RemoveResource()
194 except Error:
195 pass
197 # Create the raw data for the resource from the code object
199 data = marshal.dumps(code)
200 del code
201 data = (MAGIC + '\0\0\0\0') + data
203 # Create the resource and write it
205 id = 0
206 while id < 128:
207 id = Unique1ID(RESTYPE)
208 res = Resource(data)
209 res.AddResource(RESTYPE, id, RESNAME)
210 res.WriteResource()
211 res.ReleaseResource()
213 # Close the output file
215 CloseResFile(output)
217 # Give positive feedback
219 message("Applet %s created." % `destname`)
222 # Copy resources between two resource file descriptors.
223 # skip a resource named '__main__' or (if skipowner is set) 'Owner resource'.
224 # Also skip resources with a type listed in skiptypes.
226 def copyres(input, output, skiptypes, skipowner):
227 ctor = None
228 alltypes = []
229 UseResFile(input)
230 ntypes = Count1Types()
231 for itype in range(1, 1+ntypes):
232 type = Get1IndType(itype)
233 if type in skiptypes:
234 continue
235 alltypes.append(type)
236 nresources = Count1Resources(type)
237 for ires in range(1, 1+nresources):
238 res = Get1IndResource(type, ires)
239 id, type, name = res.GetResInfo()
240 lcname = string.lower(name)
241 if (type, lcname) == (RESTYPE, RESNAME):
242 continue # Don't copy __main__ from template
243 # XXXX should look for id=0
244 if lcname == OWNERNAME:
245 if skipowner:
246 continue # Skip this one
247 else:
248 ctor = type
249 size = res.size
250 attrs = res.GetResAttrs()
251 if DEBUG:
252 print id, type, name, size, hex(attrs)
253 res.LoadResource()
254 res.DetachResource()
255 UseResFile(output)
256 try:
257 res2 = Get1Resource(type, id)
258 except MacOS.Error:
259 res2 = None
260 if res2:
261 if DEBUG:
262 print "Overwriting..."
263 res2.RemoveResource()
264 res.AddResource(type, id, name)
265 res.WriteResource()
266 attrs = attrs | res.GetResAttrs()
267 if DEBUG:
268 print "New attrs =", hex(attrs)
269 res.SetResAttrs(attrs)
270 UseResFile(input)
271 return alltypes, ctor
274 # Show a message and exit
276 def die(str):
277 message(str)
278 sys.exit(1)
281 # Show a message
283 def message(str, id = 256):
284 from Dlg import *
285 d = GetNewDialog(id, -1)
286 if not d:
287 print "Error:", `str`
288 print "DLOG id =", id, "not found."
289 return
290 tp, h, rect = d.GetDialogItem(2)
291 SetDialogItemText(h, str)
292 d.SetDialogDefaultItem(1)
293 while 1:
294 n = ModalDialog(None)
295 if n == 1: break
296 del d
299 if __name__ == '__main__':
300 main()