#! /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
		# variables for triggering a rebuild
		self.m_vars = vars
		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"
		src_str = " ".join([a.nice_path(task.m_env) for a in task.m_inputs])
		tgt_str = " ".join([a.nice_path(task.m_env) for a in task.m_outputs])
		return "* %s : %s -> %s" % (self.m_name, src_str, tgt_str)

	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 are reserved (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 compile(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 code(self):
		lst = ['def f(task):\n\tenv=task.m_env\n\tp=env.get_flat\n\t']
		ap = lst.append
		#ap('print task.m_inputs\n\t')
		#ap('print task.m_outputs\n\t')

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

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

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

		ap('\treturn Runner.exec_command(cmd)\n')

		return "".join(lst)

	def get_fun(self):
		c = self.code()
		debug(c, 'action')
		exec(c)
		return eval('f')

def simple_action(name, line, color='GREEN', vars=[]):
	"helper provided for convenience"
	obj = alex(line)
	obj.compile()
	act = Action(name, color=color)
	act.m_function_to_run = obj.get_fun()
	act.m_vars = obj.m_vars
	if vars: act.m_vars = vars

