#############################################################################
#
#	PYROC - Python Remote Objects Proxy Compiler.
#
#	This is part of "Pyro" - Python Remote Objects
#	which is (c)1999 Irmen de Jong - irmen@bigfoot.com.
#
##############################################################################

import sys, types
import os, time
from Pyro.core import *

PYROC_VERSION = '0.2'

fileHeader = """############################################################################
#
# This code has been generated by PyroC - the Python Remote Object Compiler
#
# Do not make changes in this file!
#
############################################################################
"""

#############################################################################
#
#	CodeGenerator	- the workhorse
#
#############################################################################

class CodeGenerator:
	classes={}

	# genProxy - generates Client Proxy code		
	def genProxy(self,output):
		output.write(fileHeader)
		output.write('\n# Generated '+time.ctime(time.time())+' by PyroC V'+PYROC_VERSION+' using Pyro V'+Pyro.PYRO_VERSION+'\n\n')
		output.write('# THIS IS THE CLIENT SIDE PROXY CODE\n\nimport Pyro.core\n\n')
		for clazz in self.classes.keys():
			print 'Generating proxy for',clazz
			output.write('class '+clazz+':\n')
			output.write("""
	def __init__(self,URI):
		self.URI = URI
		self.objectID = URI.objectID
		self.adapter = Pyro.core.getProtocolAdapter(self.URI.protocol)
		self.adapter.bindToURI(URI)
""")
			# generate proxy member for each 'real member'
			for member in self.classes[clazz]:
				output.write('\tdef '+str(member)+':\n')
				flags=0
				if member.usesVarargs:
					flags=flags|RIF_Varargs
				if member.usesKeywords:
					flags=flags|RIF_Keywords
				output.write('\t\treturn self.adapter.remoteInvocation(\''+member.name+'\','+ str(flags))
				if member.hasArgs():
					output.write(','+member.argsToString(0))
				output.write(')\n')
			output.write('\n')

	# genSkeleton - generates Server Skeleton code (for now, does nothing)
	def genSkeleton(self,output):
		print 'This release of Pyro doesn\'t need server-side skeleton code.'

	# processModule - reads and processes the module for which proxies should be generated
	def processModule(self, module):
		print 'processing module \''+module.__name__+'\' ('+module.__file__+')...'
	
		# Iterate through all members of the module.
		# If it's a class, process it, otherwise we're not interested.
		for member in dir(module):
			memberInstance = getattr(module,member)
			if type(memberInstance)==types.ClassType:
				self.processClass(memberInstance,module)
			
	# processClass - processes a class for which a proxy should be generated
	def processClass(self, clazz, module):
		print 'examining class',clazz.__name__,'... ',
		# We must skip classes which were imported from other modules
		if clazz.__module__!=module.__name__:
			print 'imported from another module. Skipped.'
			return
		methods = []
		# Iterate through all members of the class.
		# If it's a member function, process it, otherwise we're not interested.
		for member in dir(clazz):
			memberInstance = getattr(clazz,member)
			if type(memberInstance)==types.MethodType:
				m = Method(member)
				# get the code object and from there, the argument names
				code = memberInstance.im_func.func_code
				argCount = code.co_argcount
				m.normalArgCount = argCount
				if code.co_flags & (1<<2):
					# method uses '*arg' parameter
					m.usesVarargs=1
					argCount = argCount+1
				if code.co_flags & (1<<3):
					# method uses '**arg' parameter
					m.usesKeywords=1
					argCount = argCount+1
				argNames = code.co_varnames[:argCount]
				m.args=argNames
				methods.append(m)	# add the method
		self.classes[clazz.__name__]=methods	# add the new class
		print len(methods),'methods processed'

#############################################################################
#
#	Method	- abstraction for a class method.
#
#############################################################################

class Method:
	name=''
	args=()
	usesVarargs=0			# uses '*arguments' syntax
	usesKeywords=0			# uses '**keywords' syntax
	normalArgCount=0		# number of 'normal' arguments (not * or **)
	
	def __init__(self,name):
		self.name=name
	def __str__(self):
		return self.name+'('+self.argsToString(1)+')'
	def hasArgs(self):
		if self.usesVarargs or self.usesKeywords:
			return 1
		return self.normalArgCount-1	# don't count self

	# argsToString - convert the method's arguments to a string.
	#  if methodDef is true, the string is used for a method DEFINITION,
	#   (where we still need the 'self' arg and the '*' and '**',
	#  otherwise it is used for a method CALL, where the
	#   'self' and '*' and '**' are omitted.

	def argsToString(self,methodDef):
		if methodDef:
			asterisk='*'
			argCnt = self.normalArgCount
			args = self.args
		else:
			asterisk=''
			argCnt = self.normalArgCount-1		# remove the 'self' argument
			args = self.args[1:]				#    ...
		s=''
		if argCnt>0:
			for arg in args[:argCnt-1]:
				s=s+arg+','
			s=s+args[argCnt-1]
			if self.usesVarargs:
				s=s+','+asterisk+args[argCnt]
				argCnt=argCnt+1		# so that the if statement below uses the next arg
			if self.usesKeywords:
				s=s+','+asterisk+asterisk+args[argCnt]
		else:
			# no args, don't add a comma
			if self.usesVarargs:
				s=s+asterisk+args[argCnt]
				argCnt=argCnt+1		# so that the if statement below uses the next arg
			if self.usesKeywords:
				if self.usesVarargs:
					# add a comma between the Vargs and the Kargs
					s=s+','
				s=s+asterisk+asterisk+args[argCnt]
		return s



############ Fire up the barbecue ##########

def main():
	args = sys.argv[1:]
	if len(args) != 1:
		print 'You must provide one argument: the name of a module to process.'
		raise SystemExit
	
	print 'Python Remote Object Compiler (c)1999 by Irmen de Jong. Pyro V'+Pyro.PYRO_VERSION+'\n'

	cwd = os.getcwd()
	if not cwd in sys.path:
        	sys.path.insert(0,cwd)
		print '[added current directory to import path]'

	module = __import__(args[0])
	proxyGen = CodeGenerator()
	proxyGen.processModule(module)
	
	outfile_proxy = module.__name__+'_proxy.py'
	outfile_skel  = module.__name__+'_skel.py'
	
	of = open(outfile_proxy,'w')
	proxyGen.genProxy(of)
	print 'This release of Pyro doesn\'t need server-side skeleton code.'
	# of = open(outfile_skel,'w')
	# proxyGen.genSkeleton(of)
	print 'All done. Output can be found in',outfile_proxy,'.'
