#! /usr/bin/env python
# encoding: utf-8
# Thomas Nagy, 2005, 2006, 2007, 2008

VERSION="1.5.18"
APPNAME='waf'
REVISION=''
top = '.'
out = 'build'

demos = ['cpp', 'qt4', 'tex', 'ocaml', 'kde3', 'adv', 'cc', 'idl', 'docbook', 'xmlwaf', 'gnome']
zip_types = ['bz2', 'gz']

#from tokenize import *
import tokenize

import os, sys, base64, shutil, re, random, StringIO, optparse, tempfile
import Utils, Options, Build
try: from hashlib import md5
except ImportError: from md5 import md5

import Configure
Configure.autoconfig = 1

print ("------> Executing code from the top-level wscript <-----")

def sub_file(fname, lst):

	f = open(fname, 'rU')
	txt = f.read()
	f.close()

	for (key, val) in lst:
		re_pat = re.compile(key, re.M)
		txt = re_pat.sub(val, txt)

	f = open(fname, 'w')
	f.write(txt)
	f.close()

def init():
	if Options.options.setver: # maintainer only (ita)
		ver = Options.options.setver
		hexver = '0x'+ver.replace('.','0')
		sub_file('wscript', (('^VERSION=(.*)', 'VERSION="%s"' % ver), ))
		sub_file('waf-light', (('^VERSION=(.*)', 'VERSION="%s"' % ver), ))

		pats = []
		pats.append(('^WAFVERSION=(.*)', 'WAFVERSION="%s"' % ver))
		pats.append(('^HEXVERSION(.*)', 'HEXVERSION=%s' % hexver))

		try:
			import Utils
			rev = Utils.cmd_output('svnversion').strip()
			pats.append(('^WAFREVISION(.*)', 'WAFREVISION = "%s"' % rev))
		except:
			pass

		sub_file('wafadmin/Constants.py', pats)
		sys.exit(0)
	elif Options.options.waf:
		create_waf()
	elif Options.commands['check']:
		# i have never used this (ita)
		sys.path.insert(0,'')
		import test.Test
		test.Test.run_tests()
	else:
		return
	sys.exit(0)

# this function is called before any other for parsing the command-line
def set_options(opt):

	# generate waf
	opt.add_option('--make-waf', action='store_true', default=False,
		help='creates the waf script', dest='waf')

	opt.add_option('--zip-type', action='store', default='bz2',
		help='specify the zip type [Allowed values: %s]' % ' '.join(zip_types), dest='zip')

	opt.add_option('--make-batch', action='store_true', default=False,
		help='creates a convenience waf.bat file (done automatically on win32 systems)',
		dest='make_batch')

	opt.add_option('--yes', action='store_true', default=False,
		help=optparse.SUPPRESS_HELP,
		dest='yes')

	# those ones are not too interesting
	opt.add_option('--set-version', default='',
		help='sets the version number for waf releases (for the maintainer)', dest='setver')

	opt.add_option('--strip', action='store_true', default=True,
		help='shrinks waf (strip docstrings, saves ~33kb)',
		dest='strip_comments')
	opt.add_option('--nostrip', action='store_false', help='no shrinking',
		dest='strip_comments')
	opt.add_option('--tools', action='store', help='3rd party tools to add [Default: "boost,fluid,rvds"]',
		dest='add3rdparty', default='boost,fluid,rvds')
	opt.tool_options('python')

def compute_revision():
	global REVISION

	def visit(arg, dirname, names):
		for pos, name in enumerate(names):
			if name[0] == '.' or name in ['_build_', 'build']:
				del names[pos]
			elif name.endswith('.py'):
				arg.append(os.path.join(dirname, name))
	sources = []
	os.path.walk('wafadmin', visit, sources)
	sources.sort()
	m = md5()
	for source in sources:
		f = file(source,'rb')
		readBytes = 100000
		while (readBytes):
			readString = f.read(readBytes)
			m.update(readString)
			readBytes = len(readString)
		f.close()
	REVISION = m.hexdigest()

#deco_re = re.compile('def\\s+([a-zA-Z_]+)\\(')
deco_re = re.compile('(def|class)\\s+(\w+)\\(.*')
def process_decorators(body):
	lst = body.split('\n')
	accu = []
	all_deco = []
	buf = [] # put the decorator lines
	for line in lst:
		if line.startswith('@'):
			buf.append(line[1:])
		elif buf:
			name = deco_re.sub('\\2', line)
			if not name:
				raise IOError, "decorator not followed by a function!"+line
			for x in buf:
				all_deco.append("%s(%s)" % (x, name))
			accu.append(line)
			buf = []
		else:
			accu.append(line)
	return "\n".join(accu+all_deco)

def process_imports(body):
	header = '#! /usr/bin/env python\n# encoding: utf-8'
	impo = ''
	deco = ''

	if body.find('set(') > -1:
		impo += 'import sys\nif sys.hexversion < 0x020400f0: from sets import Set as set'

	return "\n".join([header, impo, body, deco])

def process_tokens(tokens):
	accu = []
	prev = tokenize.NEWLINE

	accu_deco = []
	indent = 0
	line_buf = []

	for (type, token, start, end, line) in tokens:
		if type == tokenize.NEWLINE:
			if line_buf:
				accu.append(indent * '\t')
				ln = "".join(line_buf)
				if ln == 'if __name__=="__main__":': break
				#ln = ln.replace('\n', '')
				accu.append(ln)
				accu.append('\n')
				line_buf = []
				prev = tokenize.NEWLINE
		elif type == tokenize.INDENT:
			indent += 1
		elif type == tokenize.DEDENT:
			indent -= 1
		elif type == tokenize.NAME:
			if prev == tokenize.NAME or prev == tokenize.NUMBER: line_buf.append(' ')
			line_buf.append(token)
		elif type == tokenize.NUMBER:
			if prev == tokenize.NAME or prev == tokenize.NUMBER: line_buf.append(' ')
			line_buf.append(token)
		elif type == tokenize.STRING:
			if not line_buf and token.startswith('"'): pass
			else: line_buf.append(token)
		elif type == tokenize.COMMENT:
			pass
		elif type == tokenize.OP:
			line_buf.append(token)
		else:
			if token != "\n": line_buf.append(token)

		if token != '\n':
			prev = type

	body = "".join(accu)
	return body

def sfilter(path):
	f = open(path, "r")
	if Options.options.strip_comments:
		cnt = process_tokens(tokenize.generate_tokens(f.readline))
	else:
		cnt = f.read()
	f.close()

	cnt = process_decorators(cnt)
	cnt = process_imports(cnt)
	if path.endswith('Options.py') or path.endswith('Scripting.py'):
		cnt = cnt.replace('Utils.python_24_guard()', '')

	return (StringIO.StringIO(cnt), len(cnt), cnt)

def create_waf():
	print "-> preparing waf"
	mw = 'tmp-waf-'+VERSION

	import tarfile, re

	zipType = Options.options.zip.strip().lower()
	if zipType not in zip_types:
		zipType = zip_types[0]

	#open a file as tar.[extension] for writing
	tar = tarfile.open('%s.tar.%s' % (mw, zipType), "w:%s" % zipType)
	tarFiles=[]

	files = []
	add3rdparty = [x + '.py' for x in Options.options.add3rdparty.split(',')]
	for d in '. Tools 3rdparty'.split():
		dd = os.path.join('wafadmin', d)
		for k in os.listdir(dd):
			if d == '3rdparty':
				if not k in add3rdparty:
					continue
			if k.endswith('.py'):
				files.append(os.path.join(dd, k))
	for x in files:
		tarinfo = tar.gettarinfo(x, x)
		tarinfo.uid = tarinfo.gid = 0
		tarinfo.uname = tarinfo.gname = "root"
		(code, size, cnt) = sfilter(x)
		tarinfo.size = size
		tar.addfile(tarinfo, code)
	tar.close()

	f = open('waf-light', 'rb')
	code1 = f.read()
	f.close()

	# now store the revision unique number in waf
	#compute_revision()
	#reg = re.compile('^REVISION=(.*)', re.M)
	#code1 = reg.sub(r'REVISION="%s"' % REVISION, code1)

	prefix = ''
	if Build.bld:
		prefix = Build.bld.env['PREFIX'] or ''

	reg = re.compile('^INSTALL=(.*)', re.M)
	code1 = reg.sub(r'INSTALL=%r' % prefix, code1)
	#change the tarfile extension in the waf script
	reg = re.compile('bz2', re.M)
	code1 = reg.sub(zipType, code1)
	if zipType == 'gz':
		code1 = code1.replace('bunzip2', 'gzip -d')

	f = open('%s.tar.%s' % (mw, zipType), 'rb')
	cnt = f.read()
	f.close()

	# the REVISION value is the md5 sum of the binary blob (facilitate audits)
	m = md5()
	m.update(cnt)
	REVISION = m.hexdigest()
	reg = re.compile('^REVISION=(.*)', re.M)
	code1 = reg.sub(r'REVISION="%s"' % REVISION, code1)

	def find_unused(kd, ch):
		for i in xrange(35, 125):
			for j in xrange(35, 125):
				if i==j: continue
				if i == 39 or j == 39: continue
				if i == 92 or j == 92: continue
				s = chr(i) + chr(j)
				if -1 == kd.find(s):
					return (kd.replace(ch, s), s)
		raise

	# The reverse order prevent collisions
	(cnt, C2) = find_unused(cnt, '\r')
	(cnt, C1) = find_unused(cnt, '\n')
	f = open('waf', 'wb')
	f.write(code1.replace("C1='x'", "C1='%s'" % C1).replace("C2='x'", "C2='%s'" % C2))
	f.write('#==>\n')
	f.write('#')
	f.write(cnt)
	f.write('\n')
	f.write('#<==\n')
	f.close()

	if sys.platform == 'win32' or Options.options.make_batch:
		f = open('waf.bat', 'wb')
		f.write('@python -x %~dp0waf %* & exit /b\n')
		f.close()

	if sys.platform != 'win32':
		os.chmod('waf', 0755)
	os.unlink('%s.tar.%s' % (mw, zipType))

def make_copy(inf, outf):
	(a, b, cnt) = sfilter(inf)
	f = open(outf, "wb")
	f.write(cnt)
	f.close()

def configure(conf):
	conf.check_tool('python')
	conf.check_python_version((2,4))


def build(bld):

	import shutil, re

	if Options.commands['install']:
		if sys.platform == 'win32':
			print "Installing Waf on Windows is not possible."
			sys.exit(0)

	if Options.is_install:
		compute_revision()

	if Options.commands['install']:
		val = Options.options.yes or (not sys.stdin.isatty() or raw_input("Installing Waf is discouraged. Proceed? [y/n]"))
		if val != True and val != "y": sys.exit(1)
		create_waf()

	dir = os.path.join('lib', 'waf-%s-%s' % (VERSION, REVISION), 'wafadmin')

	wafadmin = bld(features = 'py')
	wafadmin.find_sources_in_dirs('wafadmin', exts=['.py'])
	wafadmin.install_path = os.path.join('${PREFIX}', dir)

	tools = bld(features = 'py')
	tools.find_sources_in_dirs('wafadmin/Tools', exts=['.py'])
	tools.install_path = os.path.join('${PREFIX}', dir, 'Tools')

	bld.install_files('${PREFIX}/bin', 'waf', chmod=0755)

	#print "waf is now installed in %s [%s, %s]" % (prefix, wafadmindir, binpath)
	#print "make sure the PATH contains %s/bin:$PATH" % prefix


#def dist():
#	import Scripting
#	Scripting.g_dist_exts += ['Weak.py'] # shows how to exclude a file from dist
#	Scripting.Dist(APPNAME, VERSION)

