# Look for scriptable applications -- that is, applications with an 'aete' resource
# Also contains (partially) reverse engineered 'aete' resource decoding

import MacOS
import os
import string
import sys
import types
import StringIO

from Carbon.Res import *

def main():
	filename = ""
	redirect(filename, realmain)

def redirect(filename, func, *args):
	f = filename and open(filename, 'w')
	save_stdout = sys.stdout
	try:
		if f: sys.stdout = f
		return apply(func, args)
	finally:
		sys.stdout = save_stdout
		if f: f.close()

def realmain():
	#list('C:System Folder:Extensions:AppleScript\252')
	#list('C:Tao AppleScript:Finder Liaison:Finder Liaison 1.0')
	list('C:Tao AppleScript:Scriptable Text Editor')
	#list('C:Internet:Eudora 1.4.2:Eudora1.4.2')
	#list('E:Excel 4.0:Microsoft Excel')
	#list('C:Internet:Netscape 1.0N:Netscape 1.0N')
	#find('C:')
	#find('D:')
	#find('E:')
	#find('F:')

def find(dir, maxlevel = 5):
	hits = []
	cur = CurResFile()
	names = os.listdir(dir)
	tuples = map(lambda x: (os.path.normcase(x), x), names)
	tuples.sort()
	names = map(lambda (x, y): y, tuples)
	for name in names:
		if name in (os.curdir, os.pardir): continue
		fullname = os.path.join(dir, name)
		if os.path.islink(fullname):
			pass
		if os.path.isdir(fullname):
			if maxlevel > 0:
				sys.stderr.write("        %s\n" % `fullname`)
				hits = hits + find(fullname, maxlevel-1)
		else:
			ctor, type = MacOS.GetCreatorAndType(fullname)
			if type in ('APPL', 'FNDR', 'zsys', 'INIT', 'scri', 'cdev'):
				sys.stderr.write("    %s\n" % `fullname`)
				try:
					rf = OpenRFPerm(fullname, 0, '\1')
				except MacOS.Error, msg:
					print "Error:", fullname, msg
					continue
				UseResFile(rf)
				n = Count1Resources('aete')
				if rf <> cur:
					CloseResFile(rf)
					UseResFile(cur)
				if n > 1:
					hits.append(fullname)
					sys.stderr.write("YES!  %d in %s\n" % (n, `fullname`))
					list(fullname)
	return hits

def list(fullname):
	cur = CurResFile()
	rf = OpenRFPerm(fullname, 0, '\1')
	try:
		UseResFile(rf)
		resources = []
		for i in range(Count1Resources('aete')):
			res = Get1IndResource('aete', 1+i)
			resources.append(res)
		for i in range(Count1Resources('aeut')):
			res = Get1IndResource('aeut', 1+i)
			resources.append(res)
		print "\nLISTING aete+aeut RESOURCES IN", `fullname`
		for res in resources:
			print "decoding", res.GetResInfo(), "..."
			data = res.data
			try:
				aete = decode(data)
				showaete(aete)
				print "Checking putaete..."
				f = StringIO.StringIO()
				putaete(f, aete)
				newdata = f.getvalue()
				if len(newdata) == len(data):
					if newdata == data:
						print "putaete created identical data"
					else:
						newaete = decode(newdata)
						if newaete == aete:
							print "putaete created equivalent data"
						else:
							print "putaete failed the test:"
							showaete(newaete)
				else:
					print "putaete created different data:"
					print `newdata`
			except:
				import traceback
				traceback.print_exc()
			sys.stdout.flush()
	finally:
		if rf <> cur:
			CloseResFile(rf)
			UseResFile(cur)

def decode(data):
	f = StringIO.StringIO(data)
	aete = generic(getaete, f)
	aete = simplify(aete)
	processed = f.tell()
	unprocessed = len(f.read())
	total = f.tell()
	if unprocessed:
		sys.stderr.write("%d processed + %d unprocessed = %d total\n" %
		                 (processed, unprocessed, total))
	return aete

def simplify(item):
	if type(item) is types.ListType:
		return map(simplify, item)
	elif type(item) == types.TupleType and len(item) == 2:
		return simplify(item[1])
	else:
		return item


# Here follows the aete resource decoder.
# It is presented bottom-up instead of top-down because there are  direct
# references to the lower-level part-decoders from the high-level part-decoders.

def getbyte(f, *args):
	c = f.read(1)
	if not c:
		raise EOFError, 'in getbyte' + str(args)
	return ord(c)

def getword(f, *args):
	getalign(f)
	s = f.read(2)
	if len(s) < 2:
		raise EOFError, 'in getword' + str(args)
	return (ord(s[0])<<8) | ord(s[1])

def getlong(f, *args):
	getalign(f)
	s = f.read(4)
	if len(s) < 4:
		raise EOFError, 'in getlong' + str(args)
	return (ord(s[0])<<24) | (ord(s[1])<<16) | (ord(s[2])<<8) | ord(s[3])

def getostype(f, *args):
	getalign(f)
	s = f.read(4)
	if len(s) < 4:
		raise EOFError, 'in getostype' + str(args)
	return s

def getpstr(f, *args):
	c = f.read(1)
	if len(c) < 1:
		raise EOFError, 'in getpstr[1]' + str(args)
	nbytes = ord(c)
	if nbytes == 0: return ''
	s = f.read(nbytes)
	if len(s) < nbytes:
		raise EOFError, 'in getpstr[2]' + str(args)
	return s

def getalign(f):
	if f.tell() & 1:
		c = f.read(1)
		##if c <> '\0':
		##	print 'align:', `c`

def getlist(f, description, getitem):
	count = getword(f)
	list = []
	for i in range(count):
		list.append(generic(getitem, f))
		getalign(f)
	return list

def alt_generic(what, f, *args):
	print "generic", `what`, args
	res = vageneric(what, f, args)
	print '->', `res`
	return res

def generic(what, f, *args):
	if type(what) == types.FunctionType:
		return apply(what, (f,) + args)
	if type(what) == types.ListType:
		record = []
		for thing in what:
			item = apply(generic, thing[:1] + (f,) + thing[1:])
			record.append((thing[1], item))
		return record
	return "BAD GENERIC ARGS: %s" % `what`

getdata = [
	(getostype, "type"),
	(getpstr, "description"),
	(getword, "flags")
	]
getargument = [
	(getpstr, "name"),
	(getostype, "keyword"),
	(getdata, "what")
	]
getevent = [
	(getpstr, "name"),
	(getpstr, "description"),
	(getostype, "suite code"),
	(getostype, "event code"),
	(getdata, "returns"),
	(getdata, "accepts"),
	(getlist, "optional arguments", getargument)
	]
getproperty = [
	(getpstr, "name"),
	(getostype, "code"),
	(getdata, "what")
	]
getelement = [
	(getostype, "type"),
	(getlist, "keyform", getostype)
	]
getclass = [
	(getpstr, "name"),
	(getostype, "class code"),
	(getpstr, "description"),
	(getlist, "properties", getproperty),
	(getlist, "elements", getelement)
	]
getcomparison = [
	(getpstr, "operator name"),
	(getostype, "operator ID"),
	(getpstr, "operator comment"),
	]
getenumerator = [
	(getpstr, "enumerator name"),
	(getostype, "enumerator ID"),
	(getpstr, "enumerator comment")
	]
getenumeration = [
	(getostype, "enumeration ID"),
	(getlist, "enumerator", getenumerator)
	]
getsuite = [
	(getpstr, "suite name"),
	(getpstr, "suite description"),
	(getostype, "suite ID"),
	(getword, "suite level"),
	(getword, "suite version"),
	(getlist, "events", getevent),
	(getlist, "classes", getclass),
	(getlist, "comparisons", getcomparison),
	(getlist, "enumerations", getenumeration)
	]
getaete = [
	(getword, "major/minor version in BCD"),
	(getword, "language code"),
	(getword, "script code"),
	(getlist, "suites", getsuite)
	]


# Display 'aete' resources in a friendly manner.
# This one's done top-down again...

def showaete(aete):
	[version, language, script, suites] = aete
	major, minor = divmod(version, 256)
	print "\nVersion %d/%d, language %d, script %d" % \
		(major, minor, language, script)
	for suite in suites:
		showsuite(suite)

def showsuite(suite):
	[name, desc, code, level, version, events, classes, comps, enums] = suite
	print "\nSuite %s -- %s (%s)" % (`name`, `desc`, `code`)
	print "Level %d, version %d" % (level, version)
	for event in events:
		showevent(event)
	for cls in classes:
		showclass(cls)
	for comp in comps:
		showcomparison(comp)
	for enum in enums:
		showenumeration(enum)

def showevent(event):
	[name, desc, code, subcode, returns, accepts, arguments] = event
	print "\n    Command %s -- %s (%s, %s)" % (`name`, `desc`, `code`, `subcode`)
	print "        returns", showdata(returns)
	print "        accepts", showdata(accepts)
	for arg in arguments:
		showargument(arg)

def showargument(arg):
	[name, keyword, what] = arg
	print "        %s (%s)" % (name, `keyword`), showdata(what)

def showclass(cls):
	[name, code, desc, properties, elements] = cls
	print "\n    Class %s (%s) -- %s" % (`name`, `code`, `desc`)
	for prop in properties:
		showproperty(prop)
	for elem in elements:
		showelement(elem)

def showproperty(prop):
	[name, code, what] = prop
	print "        property %s (%s)" % (`name`, `code`), showdata(what)

def showelement(elem):
	[code, keyform] = elem
	print "        element %s" % `code`, "as", keyform

def showcomparison(comp):
	[name, code, comment] = comp
	print "    comparison  %s (%s) -- %s" % (`name`, `code`, comment)

def showenumeration(enum):
	[code, items] = enum
	print "\n    Enum %s" % `code`
	for item in items:
		showenumerator(item)

def showenumerator(item):
	[name, code, desc] = item
	print "        %s (%s) -- %s" % (`name`, `code`, `desc`)

def showdata(data):
	[type, description, flags] = data
	return "%s -- %s %s" % (`type`, `description`, showdataflags(flags))

dataflagdict = {15: "optional", 14: "list", 13: "enum", 12: "mutable"}
def showdataflags(flags):
	bits = []
	for i in range(16):
		if flags & (1<<i):
			if i in dataflagdict.keys():
				bits.append(dataflagdict[i])
			else:
				bits.append(`i`)
	return '[%s]' % string.join(bits)


# Write an 'aete' resource.
# Closedly modelled after showaete()...

def putaete(f, aete):
	[version, language, script, suites] = aete
	putword(f, version)
	putword(f, language)
	putword(f, script)
	putlist(f, suites, putsuite)

def putsuite(f, suite):
	[name, desc, code, level, version, events, classes, comps, enums] = suite
	putpstr(f, name)
	putpstr(f, desc)
	putostype(f, code)
	putword(f, level)
	putword(f, version)
	putlist(f, events, putevent)
	putlist(f, classes, putclass)
	putlist(f, comps, putcomparison)
	putlist(f, enums, putenumeration)

def putevent(f, event):
	[name, desc, eventclass, eventid, returns, accepts, arguments] = event
	putpstr(f, name)
	putpstr(f, desc)
	putostype(f, eventclass)
	putostype(f, eventid)
	putdata(f, returns)
	putdata(f, accepts)
	putlist(f, arguments, putargument)

def putargument(f, arg):
	[name, keyword, what] = arg
	putpstr(f, name)
	putostype(f, keyword)
	putdata(f, what)

def putclass(f, cls):
	[name, code, desc, properties, elements] = cls
	putpstr(f, name)
	putostype(f, code)
	putpstr(f, desc)
	putlist(f, properties, putproperty)
	putlist(f, elements, putelement)

putproperty = putargument

def putelement(f, elem):
	[code, parts] = elem
	putostype(f, code)
	putlist(f, parts, putostype)

def putcomparison(f, comp):
	[name, id, comment] = comp
	putpstr(f, name)
	putostype(f, id)
	putpstr(f, comment)

def putenumeration(f, enum):
	[code, items] = enum
	putostype(f, code)
	putlist(f, items, putenumerator)

def putenumerator(f, item):
	[name, code, desc] = item
	putpstr(f, name)
	putostype(f, code)
	putpstr(f, desc)

def putdata(f, data):
	[type, description, flags] = data
	putostype(f, type)
	putpstr(f, description)
	putword(f, flags)

def putlist(f, list, putitem):
	putword(f, len(list))
	for item in list:
		putitem(f, item)
		putalign(f)

def putalign(f):
	if f.tell() & 1:
		f.write('\0')

def putbyte(f, value):
	f.write(chr(value))

def putword(f, value):
	putalign(f)
	f.write(chr((value>>8)&0xff))
	f.write(chr(value&0xff))

def putostype(f, value):
	putalign(f)
	if type(value) != types.StringType or len(value) != 4:
		raise TypeError, "ostype must be 4-char string"
	f.write(value)

def putpstr(f, value):
	if type(value) != types.StringType or len(value) > 255:
		raise TypeError, "pstr must be string <= 255 chars"
	f.write(chr(len(value)) + value)


# Call the main program

if __name__ == '__main__':
	main()
else:
	realmain()
