#! /usr/bin/env python
# encoding: utf-8
# Thomas Nagy, 2005 (ita)

"Actions are used to build the nodes of most tasks"

import Object, Runner, Params
from Params import debug, fatal

g_actions={}
"global actions"

class Action:
	"Base class for all Actions"
	def __init__(self, name, vars=[], func=None, color='GREEN'):
		"""If the action is simple, func is not defined, else a function can be attached
		and will be launched instead of running the string generated by 'setstr' see Runner
		for when this is used - a parameter is given, it is the task. Each action must name"""

		self.m_name = name
		self.m_vars = vars # variables that should trigger a rebuild
		self.m_function_to_run = func
		self._add_action()
		self.m_color = color

	def __str__(self):
		return self.m_name

	def _add_action(self):
		global g_actions
		if self.m_name in g_actions: debug('overriding action '+self.m_name, 'action')
		g_actions[self.m_name] = self
		debug("action added: %s" % self.m_name, 'action')

	def get_str(self, task):
		"string to display to the user"
		try:
			if Params.g_verbose:
				src_str = " ".join(map(lambda a:a.abspath(task.m_env), task.m_inputs))
				tgt_str = " ".join(map(lambda a:a.abspath(task.m_env), task.m_outputs))
				return "* %s : %s -> %s" % (self.m_name, src_str, tgt_str)
			else:
				src_str = " ".join(map(lambda a:a.nice_path(task.m_env), task.m_inputs))
				tgt_str = " ".join(map(lambda a:a.nice_path(task.m_env), task.m_outputs))
				return "* %s : %s -> %s" % (self.m_name, src_str, tgt_str)
		except:
			print "exception"
			task.debug(level=1)
			raise

	def prepare(self, task):
		"prepare the compilation"
		task.m_sig = Object.sign_env_vars(task.m_env, self.m_vars)

	def run(self, task):
		"run the compilation"
		if not self.m_function_to_run:
			fatal(self.m_name+" action has no function !")
		return self.m_function_to_run(task)

class alex:
	"""
	Actions declared using a string are compiled before use:

	A class with the necessary functions is created (so the string is parsed only once)
	All variables (CXX, ..) can be strings or lists of strings (only)
	The keywords TGT and SRC cannot be overridden (they represent the task input and output nodes)

	Example:
	    - str = '${CXX} -o ${TGT[0]} ${SRC[0]} -I ${SRC[0].m_parent.bldpath()}'
	    - act = simple_action('name', str)
	"""
	def __init__(self, s):
		self.str = s
		self.out = []
		self.params = []
		self.m_vars = []

		self.i = 0
		self.size = len(self.str)

	def start(self):
		while self.i < self.size:
			# quoted '$'
			c = self.str[self.i]
			if c == '\\':
				if self.i < self.size - 1 and self.str[self.i+1]=='$':
					self.out.append('$')
					self.i += 1
				else:
					self.out.append(c)
			elif c == '$':
				if self.str[self.i+1]=='{':
					self.i += 2
					self.varmatch()
				else:
					self.out.append(c)
			else:
				self.out.append(c)
			self.i += 1
	def varmatch(self):
		name = []
		meth = []

		cur = self.i
		while cur < self.size:
			if self.str[cur] == '}':
				s = ''.join(name)
				self.params.append( (''.join(name), ''.join(meth)) )
				self.out.append('%s')

				self.i = cur
				break
			else:
				c = self.str[cur]
				if meth:
					meth.append(c)
				else:
					if c=='.' or c =='[':
						meth.append(c)
					else:
						name.append(c)
			cur += 1
	def res(self):
		lst = ['def f(task):\n\tenv=task.m_env\n\tp=env.get_flat\n\t']

		#lst.append('print task.m_inputs\n\t')
		#lst.append('print task.m_outputs\n\t')

		lst.append('try: cmd = "')
		lst += self.out
		lst.append('"')

		alst=[]
		for (name, meth) in self.params:
			if name == 'SRC':
				if meth: alst.append('task.m_inputs%s' % meth)
				else: alst.append('" ".join(map(lambda a:a.srcpath(env), task.m_inputs))')
			elif name == 'TGT':
				if meth: alst.append('task.m_outputs%s' % meth)
				else: alst.append('" ".join(map(lambda a:a.bldpath(env), task.m_outputs))')
			else:
				self.m_vars.append(name)
				alst.append("p('%s')" % name)
		if alst:
			lst.append(' % (\\\n\t\t')
			lst += ", \\\n\t\t".join(alst)
			lst.append(')\n')

		lst.append('\texcept:\n')
		lst.append('\t\ttask.debug()\n')
		lst.append('\t\traise\n')

		lst.append('\treturn Runner.exec_command(cmd)\n')

		return "".join(lst)

	def fun(self):
		exec(self.res())
		return eval('f')

def simple_action(name, line, color='GREEN', vars=[]):
	"helper provided for convenience"
	obj = alex(line)
	obj.start()
	f = obj.fun()
	debug(obj.res(), 'action')
	act = Action(name, color=color)
	act.m_function_to_run = f
	act.m_vars = obj.m_vars
	if vars: act.m_vars = vars

