Oops -- Lib/Test should be Lib/test, of course!
[python/dscho.git] / Mac / scripts / BuildApplet.py
blob77a5eda40848a51e6c8ae85809373e63891af6bd
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 *
22 import macostools
23 import EasyDialogs
25 # .pyc file (and 'PYC ' resource magic number)
26 MAGIC = imp.get_magic()
28 # Template file (searched on sys.path)
29 TEMPLATE = "PythonApplet"
31 # Specification of our resource
32 RESTYPE = 'PYC '
33 RESNAME = '__main__'
35 # A resource with this name sets the "owner" (creator) of the destination
36 # XXXX Should look for id=0
37 OWNERNAME = "owner resource"
39 # OpenResFile mode parameters
40 READ = 1
41 WRITE = 2
43 def findtemplate():
44 """Locate the applet template along sys.path"""
45 for p in sys.path:
46 template = os.path.join(p, TEMPLATE)
47 try:
48 template, d1, d2 = macfs.ResolveAliasFile(template)
49 break
50 except (macfs.error, ValueError):
51 continue
52 else:
53 die("Template %s not found on sys.path" % `TEMPLATE`)
54 return
55 template = template.as_pathname()
56 return template
58 def main():
59 global DEBUG
60 DEBUG=1
62 # Find the template
63 # (there's no point in proceeding if we can't find it)
65 template = findtemplate()
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 or applet:', 'TEXT', 'APPL')
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 + '.out'
79 dstfss, ok = macfs.StandardPutFile('Save application as:', tf)
80 if not ok: return
81 dstfilename = dstfss.as_pathname()
82 cr, tp = MacOS.GetCreatorAndType(filename)
83 if tp == 'APPL':
84 update(template, filename, dstfilename)
85 else:
86 process(template, filename, dstfilename)
87 else:
89 # Loop over all files to be processed
90 for filename in sys.argv[1:]:
91 cr, tp = MacOS.GetCreatorAndType(filename)
92 if tp == 'APPL':
93 update(template, filename, '')
94 else:
95 process(template, filename, '')
97 def process(template, filename, output):
99 if DEBUG:
100 progress = EasyDialogs.ProgressBar("Processing %s..."%os.path.split(filename)[1], 120)
101 progress.label("Compiling...")
102 else:
103 progress = None
105 # Read the source and compile it
106 # (there's no point overwriting the destination if it has a syntax error)
108 fp = open(filename)
109 text = fp.read()
110 fp.close()
111 try:
112 code = compile(text, filename, "exec")
113 except (SyntaxError, EOFError):
114 die("Syntax error in script %s" % `filename`)
115 return
117 # Set the destination file name
119 if string.lower(filename[-3:]) == ".py":
120 destname = filename[:-3]
121 rsrcname = destname + '.rsrc'
122 else:
123 destname = filename + ".applet"
124 rsrcname = filename + '.rsrc'
126 if output:
127 destname = output
129 process_common(template, progress, code, rsrcname, destname, 0)
131 def update(template, filename, output):
132 if DEBUG:
133 progress = EasyDialogs.ProgressBar("Updating %s..."%os.path.split(filename)[1], 120)
134 else:
135 progress = None
136 if not output:
137 output = filename + ' (updated)'
138 process_common(template, progress, None, filename, output, 1)
141 def process_common(template, progress, code, rsrcname, destname, is_update):
142 # Try removing the output file
143 try:
144 os.unlink(destname)
145 except os.error:
146 pass
149 # Create FSSpecs for the various files
151 template_fss = macfs.FSSpec(template)
152 template_fss, d1, d2 = macfs.ResolveAliasFile(template_fss)
153 dest_fss = macfs.FSSpec(destname)
155 # Copy data (not resources, yet) from the template
156 if DEBUG:
157 progress.label("Copy data fork...")
158 progress.set(10)
160 tmpl = open(template, "rb")
161 dest = open(destname, "wb")
162 data = tmpl.read()
163 if data:
164 dest.write(data)
165 dest.close()
166 tmpl.close()
168 # Open the output resource fork
170 if DEBUG:
171 progress.label("Copy resources...")
172 progress.set(20)
173 try:
174 output = FSpOpenResFile(dest_fss, WRITE)
175 except MacOS.Error:
176 CreateResFile(destname)
177 output = FSpOpenResFile(dest_fss, WRITE)
179 # Copy the resources from the target specific resource template, if any
180 typesfound, ownertype = [], None
181 try:
182 input = FSpOpenResFile(rsrcname, READ)
183 except (MacOS.Error, ValueError):
184 pass
185 if DEBUG:
186 progress.inc(50)
187 else:
188 if is_update:
189 skip_oldfile = ['cfrg']
190 else:
191 skip_oldfile = []
192 typesfound, ownertype = copyres(input, output, skip_oldfile, 0, progress)
193 CloseResFile(input)
195 # Check which resource-types we should not copy from the template
196 skiptypes = []
197 if 'SIZE' in typesfound: skiptypes.append('SIZE')
198 if 'BNDL' in typesfound: skiptypes = skiptypes + ['BNDL', 'FREF', 'icl4',
199 'icl8', 'ics4', 'ics8', 'ICN#', 'ics#']
200 skipowner = (ownertype <> None)
202 # Copy the resources from the template
204 input = FSpOpenResFile(template_fss, READ)
205 dummy, tmplowner = copyres(input, output, skiptypes, skipowner, progress)
206 if ownertype == None:
207 ownertype = tmplowner
208 CloseResFile(input)
209 if ownertype == None:
210 die("No owner resource found in either resource file or template")
212 # Now set the creator, type and bundle bit of the destination
213 dest_finfo = dest_fss.GetFInfo()
214 dest_finfo.Creator = ownertype
215 dest_finfo.Type = 'APPL'
216 dest_finfo.Flags = dest_finfo.Flags | MACFS.kHasBundle
217 dest_finfo.Flags = dest_finfo.Flags & ~MACFS.kHasBeenInited
218 dest_fss.SetFInfo(dest_finfo)
220 # Make sure we're manipulating the output resource file now
222 UseResFile(output)
224 if code:
225 # Delete any existing 'PYC ' resource named __main__
227 try:
228 res = Get1NamedResource(RESTYPE, RESNAME)
229 res.RemoveResource()
230 except Error:
231 pass
233 # Create the raw data for the resource from the code object
234 if DEBUG:
235 progress.label("Write PYC resource...")
236 progress.set(120)
238 data = marshal.dumps(code)
239 del code
240 data = (MAGIC + '\0\0\0\0') + data
242 # Create the resource and write it
244 id = 0
245 while id < 128:
246 id = Unique1ID(RESTYPE)
247 res = Resource(data)
248 res.AddResource(RESTYPE, id, RESNAME)
249 res.WriteResource()
250 res.ReleaseResource()
252 # Close the output file
254 CloseResFile(output)
256 macostools.touched(dest_fss)
257 if DEBUG:
258 progress.label("Done.")
261 # Copy resources between two resource file descriptors.
262 # skip a resource named '__main__' or (if skipowner is set) 'Owner resource'.
263 # Also skip resources with a type listed in skiptypes.
265 def copyres(input, output, skiptypes, skipowner, progress=None):
266 ctor = None
267 alltypes = []
268 UseResFile(input)
269 ntypes = Count1Types()
270 progress_type_inc = 50/ntypes
271 for itype in range(1, 1+ntypes):
272 type = Get1IndType(itype)
273 if type in skiptypes:
274 continue
275 alltypes.append(type)
276 nresources = Count1Resources(type)
277 progress_cur_inc = progress_type_inc/nresources
278 for ires in range(1, 1+nresources):
279 res = Get1IndResource(type, ires)
280 id, type, name = res.GetResInfo()
281 lcname = string.lower(name)
282 ## if (type, lcname) == (RESTYPE, RESNAME):
283 ## continue # Don't copy __main__ from template
284 # XXXX should look for id=0
285 if lcname == OWNERNAME:
286 if skipowner:
287 continue # Skip this one
288 else:
289 ctor = type
290 size = res.size
291 attrs = res.GetResAttrs()
292 if DEBUG and progress:
293 progress.label("Copy %s %d %s"%(type, id, name))
294 progress.inc(progress_cur_inc)
295 res.LoadResource()
296 res.DetachResource()
297 UseResFile(output)
298 try:
299 res2 = Get1Resource(type, id)
300 except MacOS.Error:
301 res2 = None
302 if res2:
303 if DEBUG and progress:
304 progress.label("Overwrite %s %d %s"%(type, id, name))
305 res2.RemoveResource()
306 res.AddResource(type, id, name)
307 res.WriteResource()
308 attrs = attrs | res.GetResAttrs()
309 res.SetResAttrs(attrs)
310 UseResFile(input)
311 return alltypes, ctor
314 # Show a message and exit
316 def die(str):
317 message(str)
318 sys.exit(1)
321 # Show a message
323 def message(str, id = 256):
324 from Dlg import *
325 d = GetNewDialog(id, -1)
326 if not d:
327 print "Error:", `str`
328 print "DLOG id =", id, "not found."
329 return
330 tp, h, rect = d.GetDialogItem(2)
331 SetDialogItemText(h, str)
332 d.SetDialogDefaultItem(1)
333 while 1:
334 n = ModalDialog(None)
335 if n == 1: break
336 del d
339 if __name__ == '__main__':
340 main()