#! /usr/bin/env python2
# -*- coding: utf-8 -*-
#======================================================================
#
# escope.py - 
#
# Created by skywind on 2016/11/02
# Last change: 2016/11/02 18:12:09
#
#======================================================================
import sys
import time
import os
import json
import hashlib
import datetime

if sys.version_info[0] >= 3:
	raise "Must be using Python 2"


#----------------------------------------------------------------------
# execute and capture
#----------------------------------------------------------------------
def execute(args, shell = False, capture = False):
	import sys, os
	parameters = []
	if type(args) in (type(''), type(u'')):
		import shlex
		cmd = args
		if sys.platform[:3] == 'win':
			ucs = False
			if type(cmd) == type(u''):
				cmd = cmd.encode('utf-8')
				ucs = True
			args = shlex.split(cmd.replace('\\', '\x00'))
			args = [ n.replace('\x00', '\\') for n in args ]
			if ucs:
				args = [ n.decode('utf-8') for n in args ]
		else:
			args = shlex.split(cmd)
	for n in args:
		if sys.platform[:3] != 'win':
			replace = { ' ':'\\ ', '\\':'\\\\', '\"':'\\\"', '\t':'\\t', \
				'\n':'\\n', '\r':'\\r' }
			text = ''.join([ replace.get(ch, ch) for ch in n ])
			parameters.append(text)
		else:
			if (' ' in n) or ('\t' in n) or ('"' in n): 
				parameters.append('"%s"'%(n.replace('"', ' ')))
			else:
				parameters.append(n)
	cmd = ' '.join(parameters)
	if sys.platform[:3] == 'win' and len(cmd) > 255:
		shell = False
	if shell and (not capture):
		os.system(cmd)
		return ''
	elif (not shell) and (not capture):
		import subprocess
		if 'call' in subprocess.__dict__:
			subprocess.call(args)
			return ''
	import subprocess
	if 'Popen' in subprocess.__dict__:
		if sys.platform[:3] != 'win' and shell:
			p = None
			stdin, stdouterr = os.popen4(cmd)
		else:
			p = subprocess.Popen(args, shell = shell,
					stdin = subprocess.PIPE, stdout = subprocess.PIPE, 
					stderr = subprocess.STDOUT)
			stdin, stdouterr = (p.stdin, p.stdout)
	else:
		p = None
		stdin, stdouterr = os.popen4(cmd)
	text = stdouterr.read()
	stdin.close()
	stdouterr.close()
	if p: p.wait()
	if not capture:
		sys.stdout.write(text)
		sys.stdout.flush()
		return ''
	return text


#----------------------------------------------------------------------
# redirect process output to reader(what, text)
#----------------------------------------------------------------------
def redirect(args, reader, combine = True):
	import subprocess
	if 'Popen' in subprocess.__dict__:
		p = subprocess.Popen(args, shell = False,
			stdin = subprocess.PIPE, stdout = subprocess.PIPE,
			stderr = combine and subprocess.STDOUT or subprocess.PIPE)
		stdin, stdout, stderr = p.stdin, p.stdout, p.stderr
		if combine: stderr = None
	else:
		p = None
		if combine == False:
			stdin, stdout, stderr = os.popen3(cmd)
		else:
			stdin, stdout = os.popen4(cmd)
			stderr = None
	stdin.close()
	while 1:
		text = stdout.readline()
		if text == '':
			break
		reader('stdout', text)
	while stderr != None:
		text = stderr.readline()
		if text == '':
			break
		reader('stderr', text)
	stdout.close()
	if stderr: stderr.close()
	retcode = None
	if p:
		retcode = p.wait()
	return retcode


#----------------------------------------------------------------------
# configure
#----------------------------------------------------------------------
class configure (object):

	def __init__ (self, ininame = None):
		self.ininame = ininame
		self.unix = (sys.platform[:3] != 'win') and 1 or 0
		self.config = {}
		self.rc = None
		self._search_config()
		self._search_cscope()
		self._search_gtags()
		self._search_pycscope()
		self._search_rc()
		rc = self.option('default', 'rc', None)
		if rc and os.path.exists(rc):
			self.rc = self.abspath(rc)
		self.config['default']['rc'] = rc
		self.has_cscope = (self.option('default', 'cscope') != None)
		self.has_gtags = (self.option('default', 'gtags') != None)
		self.has_pycscope = (self.option('default', 'pycscope') != None)
		self.exename = {}
		if self.has_cscope:
			cscope = self.option('default', 'cscope')
			if self.unix:
				self.exename['cscope'] = os.path.join(cscope, 'cscope')
			else:
				self.exename['cscope'] = os.path.join(cscope, 'cscope.exe')
		if self.has_gtags:
			gtags = self.option('default', 'gtags')
			if self.unix:
				f = lambda n: os.path.join(gtags, n)
			else:
				g = lambda n: os.path.join(gtags, n + '.exe')
				f = lambda n: os.path.abspath(g(n))
			self.exename['gtags'] = f('gtags')
			self.exename['global'] = f('global')
			self.exename['gtags-cscope'] = f('gtags-cscope')
		if self.has_pycscope:
			pycscope = self.option('default', 'pycscope')
			if self.unix:
				pycscope = os.path.join(pycscope, 'pycscope')
			else:
				pycscope = os.path.join(pycscope, 'pycscope.exe')
			self.exename['pycscope'] = pycscope
		self.GetShortPathName = None
		self.database = None

	# search escope config
	def _search_config (self):
		self.config = {}
		self.config['default'] = {}
		if self.ininame and os.path.exists(self.ininame):
			self._read_ini(self.ininame)
			return 0
		fullname = os.path.abspath(__file__)
		testname = os.path.splitext(fullname)[0] + '.ini'
		if os.path.exists(testname):
			self._read_ini(testname)
			self.ininame = testname
		if self.unix:
			self._read_ini('/etc/escope.ini')
			self._read_ini('/usr/local/etc/escope.ini')
		self._read_ini(os.path.expanduser('~/.config/escope.ini'))
		return 0

	def _read_ini (self, filename):
		import ConfigParser
		if not os.path.exists(filename):
			return -1
		fp = open(filename, 'r')
		cp = ConfigParser.ConfigParser(fp)
		for sect in cp.sections():
			if not sect in self.config:
				self.config[sect] = {}
			for key, value in cp.items(sect):
				self.config[sect.lower()][key.lower()] = value
		fp.close()
		return 0

	# read option 
	def option (self, sect, item, default = None):
		if not sect in self.config:
			return default
		return self.config[sect].get(item, default)

	# search cscope
	def _search_cscope (self):
		def _test_cscope(path):
			if not os.path.exists(path):
				return False
			if sys.platform[:3] != 'win':
				if not os.path.exists(os.path.join(path, 'cscope')):
					return False
			else:
				if not os.path.exists(os.path.join(path, 'cscope.exe')):
					return False
			return True
		cscope = self.option('default', 'cscope')
		if cscope:
			if _test_cscope(cscope):
				self.config['default']['cscope'] = os.path.abspath(cscope)
				return 0
		self.config['default']['cscope'] = None
		cscope = os.path.abspath(os.path.dirname(__file__))
		if _test_cscope(cscope):
			self.config['default']['cscope'] = cscope
			return 0
		PATH = os.environ.get('PATH', '').split(self.unix and ':' or ';')
		for path in PATH:
			if _test_cscope(path):
				self.config['default']['cscope'] = os.path.abspath(path)
				return 0
		return -1

	# search gtags executables
	def _search_gtags (self):
		def _test_gtags(path):
			if not os.path.exists(path):
				return False
			if sys.platform[:3] != 'win':
				if not os.path.exists(os.path.join(path, 'gtags')):
					return False
				if not os.path.exists(os.path.join(path, 'global')):
					return False
				if not os.path.exists(os.path.join(path, 'gtags-cscope')):
					return False
			else:
				if not os.path.exists(os.path.join(path, 'gtags.exe')):
					return False
				if not os.path.exists(os.path.join(path, 'global.exe')):
					return False
				if not os.path.exists(os.path.join(path, 'gtags-cscope.exe')):
					return False
			return True
		gtags = self.option('default', 'gtags')
		if gtags:
			if _test_gtags(gtags):
				self.config['default']['gtags'] = os.path.abspath(gtags)
				return 0
		self.config['default']['gtags'] = None
		gtags = os.path.abspath(os.path.dirname(__file__))
		if _test_gtags(gtags):
			self.config['default']['gtags'] = gtags
			return 0
		PATH = os.environ.get('PATH', '').split(self.unix and ':' or ';')
		for path in PATH:
			if _test_gtags(path):
				self.config['default']['gtags'] = os.path.abspath(path)
				return 0
		return -1

	# search pycscope
	def _search_pycscope (self):
		def _test_pycscope(path):
			if not os.path.exists(path):
				return False
			if sys.platform[:3] != 'win':
				if not os.path.exists(os.path.join(path, 'pycscope')):
					return False
			else:
				if not os.path.exists(os.path.join(path, 'pycscope.exe')):
					return False
			return True
		pycscope = self.option('default', 'pycscope')
		if pycscope:
			if _test_pycscope(pycscope):
				pycscope = os.path.abspath(pycscope)
				self.config['default']['pycscope'] = pycscope
				return 0
		self.config['default']['pycscope'] = None
		pycscope = os.path.abspath(os.path.dirname(__file__))
		if _test_pycscope(pycscope):
			self.config['default']['pycscope'] = pycscope
			return 0
		PATH = os.environ.get('PATH', '').split(self.unix and ':' or ';')
		for path in PATH:
			if _test_pycscope(path):
				self.config['default']['pycscope'] = os.path.abspath(path)
				return 0
		return -1

	# abspath 
	def abspath (self, path, resolve = False):
		if path == None:
			return None
		if '~' in path:
			path = os.path.expanduser(path)
		path = os.path.abspath(path)
		if not self.unix:
			return path.lower().replace('\\', '/')
		if resolve:
			return os.path.abspath(os.path.realpath(path))
		return path

	# search gtags rc
	def _search_rc (self):
		rc = self.option('default', 'rc', None)
		if rc != None:
			rc = self.abspath(rc)
			if os.path.exists(rc):
				self.config['default']['rc'] = rc
				return 0
		self.config['default']['rc'] = None
		rc = self.abspath('~/.globalrc')
		if os.path.exists(rc):
			self.config['default']['rc'] = rc
			return 0
		if self.unix:
			rc = '/etc/gtags.conf'
			if os.path.exists(rc):
				self.config['default']['rc'] = rc
				return 0
			rc = '/usr/local/etc/gtags.conf'
			if os.path.exists(rc):
				self.config['default']['rc'] = rc
				return 0
		gtags = self.option('default', 'gtags')
		if gtags == None:
			return -1
		rc = os.path.join(gtags, '../share/gtags/gtags.conf')
		rc = self.abspath(rc)
		if os.path.exists(rc):
			self.config['default']['rc'] = rc
		return -1

	# short name in windows
	def pathshort (self, path):
		path = os.path.abspath(path)
		if self.unix:
			return path
		if not self.GetShortPathName:
			self.kernel32 = None
			self.textdata = None
			try:
				import ctypes
				self.kernel32 = ctypes.windll.LoadLibrary("kernel32.dll")
				self.textdata = ctypes.create_string_buffer('\000' * 1024)
				self.GetShortPathName = self.kernel32.GetShortPathNameA
				args = [ ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int ]
				self.GetShortPathName.argtypes = args
				self.GetShortPathName.restype = ctypes.c_uint32
			except: pass
		if not self.GetShortPathName:
			return path
		retval = self.GetShortPathName(path, self.textdata, 1024)
		shortpath = self.textdata.value
		if retval <= 0:
			return ''
		return shortpath

	# recursion make directory
	def mkdir (self, path):
		path = os.path.abspath(path)
		if os.path.exists(path):
			return 0
		name = ''
		part = os.path.abspath(path).replace('\\', '/').split('/')
		if self.unix:
			name = '/'
		if (not self.unix) and (path[1:2] == ':'):
			part[0] += '/'
		for n in part:
			name = os.path.abspath(os.path.join(name, n))
			if not os.path.exists(name):
				os.mkdir(name)
		return 0

	# execute a gnu global executable
	def execute (self, name, args, capture = False, printcmd = False):
		if name in self.exename:
			name = self.exename[name]
		name = self.pathshort(name)
		#printcmd = True
		if printcmd:
			print [name] + args
		if not capture in (0, 1, True, False, None):
			return redirect([name] + args, capture)
		return execute([name] + args, False, capture)

	# initialize environment
	def init (self):
		if self.rc and os.path.exists(self.rc):
			os.environ['GTAGSCONF'] = os.path.abspath(self.rc)
		os.environ['GTAGSFORCECPP'] = '1'
		PATH = os.environ.get('PATH', '')
		gtags = self.option('default', 'gtags')
		if self.unix:
			if gtags: PATH = gtags + ':' + PATH
		else:
			if gtags: PATH = gtags + ';' + PATH
		os.environ['PATH'] = PATH
		database = self.option('default', 'database', None)
		if database:
			database = self.abspath(database, True)
		elif 'ESCOPE' in os.environ:
			escope = os.environ['ESCOPE']
			if not escope.lower() in (None, '', '/', '\\', 'c:/', 'c:\\'):
				database = self.abspath(escope)
		if database == None:
			database = self.abspath('~/.local/var/escope', True)
		if not os.path.exists(database):
			self.mkdir(database)
		if not os.path.exists(database):
			raise Exception('Cannot create database folder: %s'%database)
		self.database = database
		return 0

	# get project db path
	def pathdb (self, root):
		if (self.database == None) or (root == None):
			return None
		root = root.strip()
		root = self.abspath(root)
		hash = hashlib.md5(root).hexdigest().lower()
		hash = hash[:16]
		path = os.path.abspath(os.path.join(self.database, hash))
		return (self.unix) and path or path.replace('\\', '/')

	# load project desc
	def load (self, root):
		db = self.pathdb(root)
		if db == None:
			return None
		cfg = os.path.join(db, 'config.json')
		if not os.path.exists(cfg):
			return None
		fp = open(cfg, 'r')
		content = fp.read()
		fp.close()
		try:
			obj = json.loads(content)
		except:
			return None
		if type(obj) != type({}):
			return None
		return obj

	# save project desc
	def save (self, root, obj):
		db = self.pathdb(root)
		if db == None or type(obj) != type({}):
			return -1
		cfg = os.path.join(db, 'config.json')
		text = json.dumps(obj, indent = 4)
		fp = open(cfg, 'w')
		fp.write(text)
		fp.close()
		return 0

	def timestamp (self):
		return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")

	def strptime (self, text):
		return datetime.datetime.strptime(text, "%Y-%m-%d %H:%M:%S")

	def get_size (self, path = '.'):
		total_size = 0
		for dirpath, dirnames, filenames in os.walk(path):
			for f in filenames:
				fp = os.path.join(dirpath, f)
				total_size += os.path.getsize(fp)
		return total_size

	# list all projects in database
	def list (self, garbage = None):
		roots = []
		if garbage == None:
			garbage = []
		if self.database == None:
			return None
		if not os.path.exists(self.database):
			return None
		for name in os.listdir(self.database):
			name = name.strip()
			if len(name) != 16:
				garbage.append(name)
				continue
			path = os.path.join(self.database, name)
			if not os.path.isdir(path):
				garbage.append(name)
				continue
			desc = None
			cfg = os.path.join(path, 'config.json')
			if os.path.exists(cfg):
				try:
					fp = open(cfg, 'r')
					text = fp.read()
					fp.close()
					desc = json.loads(text)
				except:
					desc = None
				if type(desc) != type({}):
					desc = None
			root = (desc != None) and desc.get('root', '') or ''
			if desc == None or root == '':
				garbage.append(name)
				continue
			if desc.get('db', '') == '':
				garbage.append(name)
				continue
			roots.append((name, root, desc))
		return roots

	# select and initialize a project
	def select (self, root):
		if root == None:
			return None
		root = root.strip()
		root = self.abspath(root)
		db = self.pathdb(root)
		self.mkdir(db)
		os.environ['GTAGSROOT'] = os.path.abspath(root)
		os.environ['GTAGSDBPATH'] = os.path.abspath(db)
		desc = self.load(root)
		if desc:
			if not 'root' in desc:
				desc = None
			elif not 'db' in desc:
				desc = None
		if desc == None:
			desc = {}
			desc['root'] = root
			desc['db'] = db
			desc['ctime'] = self.timestamp()
			desc['mtime'] = self.timestamp()
			desc['version'] = 0
			desc['size'] = 0
			self.save(root, desc)
		return desc

	# clear invalid files in the database path
	def clear (self):
		if self.database == None:
			return -1
		if not os.path.exists(self.database):
			return -2
		if self.database == '/':
			return -3
		database = os.path.abspath(self.database)
		if len(self.database) == 3 and self.unix == 0:
			if self.database[1] == ':':
				return -4
		garbage = []
		self.list(garbage)
		import shutil
		for name in garbage:
			path = os.path.join(self.database, name)
			if not os.path.exists(path):
				continue
			if os.path.isdir(path):
				shutil.rmtree(path, True)
			else:
				try: os.remove(path)
				except: pass
		return 0


#----------------------------------------------------------------------
# escope - gtags wrapper
#----------------------------------------------------------------------
class escope (object):

	def __init__ (self, ininame = None):
		self.config = configure(ininame)
		self.desc = None
		self.root = None
		self.db = None
		self.cscope_names = ['.c', '.h', '.cpp', '.cc', '.hpp', '.hh']
		self.cscope_names += ['.go', '.java', '.js', '.m', '.mm']
		self.ignores = ('CVS', '.git', '.svn', '.hg', '.bzr')

	def init (self):
		if self.config.database != None:
			return 0
		self.config.init()
		return 0

	def select (self, root):
		self.desc = None
		self.root = None
		desc = self.config.select(root)
		if desc == None:
			return -1
		self.desc = desc
		self.root = self.config.abspath(root)
		self.db = self.desc['db']
		return 0

	def abort (self, message, code = 1):
		sys.stderr.write(message + '\n')
		sys.stderr.flush()
		sys.exit(1)
		return -1

	def check_cscope (self):
		if not self.config.has_cscope:
			self.abort('cscope executable cannot be found in $PATH')
			return False
		return True

	def check_gtags (self):
		if not self.config.has_gtags:
			msg = 'GNU Global (gtags) executables cannot be found in $PATH'
			self.abort(msg)
			return False
		return True

	def check_pycscope (self):
		if not self.config.has_pycscope:
			self.abort('pycscope executable cannot be found in $PATH')
			return False
		return True

	def find_files (self, path, extnames = None):
		result = []
		if extnames:
			if not self.config.unix:
				extnames = [ n.lower() for n in extnames ]
			extnames = tuple(extnames)
		for root, dirs, files in os.walk(path):
			for ignore in self.ignores:
				if ignore in dirs:
					dirs.remove(ignore)
			for name in files:
				if extnames:
					ext = os.path.splitext(name)[-1]
					if not self.config.unix:
						ext = ext.lower()
					if not ext in extnames:
						continue
				result.append(os.path.abspath(os.path.join(root, name)))
		return result

	def find_list (self, path, filelist):
		result = []
		lines = []
		if filelist == '-':
			for line in sys.stdin:
				lines.append(line.rstrip('\r\n\t '))
		else:
			for line in open(filelist):
				lines.append(line.rstrip('\r\n\t '))
		for line in lines:
			if not line:
				continue
			line = os.path.join(path, line)
			result.append(os.path.abspath(line))
		return result

	def cscope_generate (self, include = None, kernel = False, filelist = None, verbose = 0):
		if not self.check_cscope():
			return -1
		if (self.desc == None) or (self.root == None):
			self.abort('Project has not been selected')
			return -2
		if not filelist:
			names = self.find_files(self.root, self.cscope_names)
		else:
			names = self.find_list(self.root, filelist)
		listname = os.path.join(self.db, 'cscope.txt')
		outname = os.path.join(self.db, 'cscope.out')
		if verbose:
			for fn in names:
				print fn
			sys.stdout.flush()
		fp = open(listname, 'w')
		for line in names:
			fp.write(line + '\n')
		fp.close()
		args = ['-b']
		if kernel:
			args += ['-k']
		if include:
			for inc in include:
				args += ['-I', os.path.join(self.root, inc)]
		if self.config.unix:
			args += ['-q']
		args += ['-i', 'cscope.txt']
		savecwd = os.getcwd()
		os.chdir(self.db)
		self.config.execute('cscope', args)
		os.chdir(savecwd)
		self.desc['mtime'] = self.config.timestamp()
		self.desc['version'] = self.desc['version'] + 1
		self.config.save(self.desc['root'], self.desc)
		return 0

	def gtags_generate (self, label = None, update = False, filelist = None, verbose = False):
		if not self.check_gtags():
			return -1
		if (self.desc == None) or (self.root == None):
			self.abort('Project has not been selected')
			return -2
		args = ['--skip-unreadable']
		if label:
			args += ['--gtagslabel', label]
		if verbose:
			args += ['-v']
		if update:
			if not type(update) in (type(''), type(u'')):
				args += ['-i']
			else:
				args += ['--single-update', update]
		if filelist:
			names = self.find_list(self.root, filelist)
			listname = os.path.join(self.root, 'gtags.txt')
			fp = open(listname, 'w')
			for name in names:
				fp.write(name + '\n')
			fp.close()
			args += ['-f', listname]
		db = self.desc['db']
		args += [db]
		cwd = os.getcwd()
		os.chdir(self.root)
		self.config.execute('gtags', args)
		os.chdir(cwd)
		self.desc['mtime'] = self.config.timestamp()
		self.desc['version'] = self.desc['version'] + 1
		self.config.save(self.desc['root'], self.desc)
		return 0

	def pycscope_generate (self, filelist = None, verbose = False):
		if not self.check_pycscope():
			return -1
		if (self.desc == None) or (self.root == None):
			self.abort('Project has not been selected')
			return -2
		if not filelist:
			names = self.find_files(self.root, ['.py', '.pyw'])
		else:
			names = self.find_list(self.root, filelist)
		listname = os.path.join(self.db, 'pycscope.txt')
		outname = os.path.join(self.db, 'pycscope.out')
		if verbose:
			for fn in names:
				print fn
			sys.stdout.flush()
		fp = open(listname, 'w')
		for name in names:
			fp.write(name + '\n')
		fp.close()
		args = ['-i', 'pycscope.txt', '-f', 'pycscope.out']
		savecwd = os.getcwd()
		os.chdir(self.db)
		self.config.execute('pycscope', args)
		os.chdir(savecwd)
		self.desc['mtime'] = self.config.timestamp()
		self.desc['version'] = self.desc['version'] + 1
		self.config.save(self.desc['root'], self.desc)
		return 0

	def cscope_translate (self, where, text):
		text = text.rstrip('\r\n')
		if text == '':
			return -1
		p1 = text.find(' ')
		if p1 < 0:
			return -2
		p2 = text.find(' ', p1 + 1)
		if p2 < 0:
			return -3
		p3 = text.find(' ', p2 + 1)
		if p3 < 0:
			return -4
		cname = text[:p1]
		csymbol = text[p1 + 1:p2]
		cline = text[p2 + 1:p3]
		ctext = text[p3 + 1:]
		output = '%s:%s: <<%s>> %s'%(cname, cline, csymbol, ctext)
		sys.stdout.write(output + '\n')
		sys.stdout.flush()
		return 0

	def cscope_find (self, mode, name):
		if not self.check_cscope():
			return -1
		if (self.desc == None) or (self.root == None):
			self.abort('Project has not been selected')
			return -2
		args = ['-dl', '-L', '-f', 'cscope.out', '-' + str(mode), name]
		savecwd = os.getcwd()
		os.chdir(self.db)
		self.config.execute('cscope', args, self.cscope_translate)
		os.chdir(savecwd)
		return 0

	def pycscope_find (self, mode, name):
		if not self.check_cscope():
			return -1
		if (self.desc == None) or (self.root == None):
			self.abort('Project has not been selected')
			return -2
		args = ['-dl', '-L', '-f', 'pycscope.out', '-' + str(mode), name]
		savecwd = os.getcwd()
		os.chdir(self.db)
		self.config.execute('cscope', args, self.cscope_translate)
		os.chdir(savecwd)
		return 0

	def gtags_find (self, mode, name):
		if (self.desc == None) or (self.root == None):
			self.abort('Project has not been selected')
			return -1
		args = ['-a', '--result', 'grep']
		if mode in (0, '0', 's', 'symbol'):
			self.config.execute('global', args + ['-d', '-e', name])
			self.config.execute('global', args + ['-r', '-e', name])
			self.config.execute('global', args + ['-s', '-e', name])
		elif mode in (1, '1', 'g', 'definition'):
			self.config.execute('global', args + ['-d', '-e', name])
		elif mode in (3, '3', 'c', 'reference'):
			self.config.execute('global', args + ['-r', '-e', name])
		elif mode in (4, '4', 't', 'string', 'text'):
			self.config.execute('global', args + ['-gGo', '-e', name])
		elif mode in (6, '6', 'e', 'grep', 'egrep'):
			self.config.execute('global', args + ['-gEo', '-e', name])
		elif mode in (7, '7', 'f', 'file'):
			self.config.execute('global', args + ['-P', '-e', name])
		else:
			sys.stderr.write('unsupported')
			sys.stderr.flush()
		return 0

	def generate (self, backend, parameter, update = False, filelist = None, verbose = False):
		if (self.desc == None) or (self.root == None):
			self.abort('Project has not been selected')
			return -1
		if backend in ('cscope', 'cs'):
			kernel = True
			if parameter.lower() in ('1', 'true', 'sys', 'system'):
				kernel = False
			return self.cscope_generate(None, kernel, filelist, verbose)
		elif backend in ('pycscope', 'py'):
			return self.pycscope_generate(filelist, verbose)
		elif backend in ('gtags', 'global', 'gnu'):
			return self.gtags_generate(parameter, update, filelist, verbose)
		else:
			self.abort('unknow backend: %s'%backend)
		return 0

	def find (self, backend, mode, name):
		if (self.desc == None) or (self.root == None):
			self.abort('Project has not been selected')
			return -1
		backend = backend.split('/')
		engine = backend[0]
		parameter = len(backend) >= 2 and backend[1] or ''
		if engine in ('cscope', 'cs'):
			return self.cscope_find(mode, name)
		elif engine in ('pycscope', 'py'):
			return self.pycscope_find(mode, name)
		elif engine in ('gtags', 'global', 'gnu'):
			return self.gtags_find(mode, name)
		else:
			self.abort('unknow backend: %s'%backend)
		return 0

	def list (self):
		if self.config.database == None:
			self.abort('Initialzing is required')
			return -1
		print 'Database:', self.config.database
		print ''
		print 'Hash'.ljust(16),  'Size(KB)'.rjust(11), ' Modified'.ljust(12), ' Root'
		def add_commas(instr):
			rng = reversed(range(1, len(instr) + (len(instr) - 1)//3 + 1))
			out = [',' if j%4 == 0 else instr[-(j - j//4)] for j in rng]
			return ''.join(out)
		for name, root, desc in self.config.list():
			db = os.path.join(self.config.database, name)
			size = (self.config.get_size(db) + 1023) / 1024
			size = add_commas(str(size))
			print name, size.rjust(11), '', desc['mtime'][:10], ' ', desc['root']
		print ''
		return 0

	def clean (self, days):
		if self.config.database == None:
			self.abort('Initialzing is required')
			return -1
		self.config.clear()
		import datetime, time, shutil
		d0 = datetime.datetime.fromtimestamp(time.time())
		for name, root, desc in self.config.list():
			mtime = desc['mtime']
			path = os.path.join(self.config.database, name)
			d1 = self.config.strptime(mtime)
			dd = d0 - d1
			if dd.days >= days and os.path.exists(path):
				sys.stdout.write('%s ... '%name)
				sys.stdout.flush()
				shutil.rmtree(path)
				sys.stdout.write('(removed)\n')
				sys.stdout.flush()
		return 0


#----------------------------------------------------------------------
# errmsg
#----------------------------------------------------------------------
def errmsg(message, abort = False):
	sys.stderr.write('error: ' + message + '\n')
	sys.stderr.flush()
	if abort:
		sys.exit(2)
	return 0


#----------------------------------------------------------------------
# main
#----------------------------------------------------------------------
def main(argv = None):
	argv = (argv == None) and sys.argv or argv
	argv = [ n for n in argv ]
	if len(argv) <= 1:
		errmsg('no operation specified (use -h for help)', True)
		return -1
	operator = argv[1]
	program = os.path.split(argv[0])[-1]
	if operator in ('-h' , '--help'):
		print 'usage %s <operation> [...]'%program
		print 'operations:'
		head = '    %s '%program
		print head + '{-h --help}'
		print head + '{-V --version}'
		print head + '{-B --build} [-k backend] [-r root] [-l label] [-u] [-i] [-v] [-s]'
		print head + '{-F --find} [-k backend] [-r root] -num pattern'
		print head + '{-C --clean} [-d days]'
		print head + '{-L --list}'
		print ''
		head = '    '
		print '-k backend    Choose backend, which can be one of: cscope, gtags or pycscope.'
		print '-r root       Root path of source files, use current directory by default.'
		print '-i filelist   Give a list of candidates of target files, - for stdin.'
		print '-s            System mode - use /usr/include for #include files (cscope).'
		print '-l label      Label of gtags which can be : native, ctags, pygments ... etc.'
		print '-u            Update database only (gtags backend is required).'
		print '-num pattern  Go to cscope input field num (counting from 0) and find pattern.'
		print '-d days       Clean databases modified before given days (default is 30).'
		print '-v            Build the cross reference database in verbose mode.'
		if 0:
			print '-0 pattern    Find this C symbol'
			print '-1 pattern    Find this definition'
			print '-2 pattern    Find functions called by this function (cscope/pycscope)'
			print '-3 pattern    Find functions calling this function'
			print '-4 pattern    Find this text string'
			print '-6 pattern    Find this egrep pattern'
			print '-7 pattern    Find this file'
			print '-8 pattern    Find files #including this file'
			print '-9 pattern    Find places where this symbol is assigned a value'
		print ''
		return 0

	if operator in ('-V', '--version'):
		print 'escope: version 1.0.1'
		return 0

	if not operator in ('-B', '--build', '-F', '--find', '-L', '--list', '-C', '--clean'):
		errmsg('unknow operation: ' + operator, True)
		return -1

	es = escope()
	es.init()

	if operator in ('-L', '--list'):
		es.list()
		return 0

	options = {}
	index = 2

	while index < len(argv):
		opt = argv[index]
		if opt in ('-k', '-r', '-l', '-d', '-i'):
			if index + 1 >= len(argv):
				errmsg('not enough parameter for option: ' + opt, True)
				return -2
			options[opt] = argv[index + 1]
			index += 2
		elif opt >= '-0' and opt <= '-9' and len(opt) == 2:
			if index + 1 >= len(argv):
				errmsg('require pattern for field: ' + opt, True)
				return -2
			options['num'] = int(opt[1:])
			options['name'] = argv[index + 1]
			index += 2
		elif opt in ('-s', '-u', '-v'):
			options[opt] = True
			index += 1
		else:
			errmsg('unknow option: ' + opt, True)
			return -2

	if not '-k' in options:
		errmsg('require backend name, use one of cscope, gtags, pycscope after -k', True)
		return -3

	backend = options['-k']
	if not backend in ('cscope', 'gtags', 'pycscope'):
		errmsg('bad backend name, use one of cscope, gtags, pycscope after -k', True)
		return -3

	root = options.get('-r', os.getcwd())
	if not os.path.exists(root):
		errmsg('path does not exist: ' + root, True)
		return -3

	es.select(root)

	if operator in ('-B', '--build'):
		label = options.get('-l', '')
		if backend != 'gtags' and label != '':
			errmsg('label can only be used with gtags backend', True)
			return -5
		label = (label == '') and 'native' or label
		system = options.get('-s') and True or False
		update = options.get('-u') and True or False
		verbose = options.get('-v') and True or False
		filelist = options.get('-i', None)
		if backend != 'cscope' and system != False:
			errmsg('system mode can only be used with cscope backend', True)
			return -5
		if backend != 'gtags' and update != False:
			errmsg('update mode can only be used with gtags backend', True)
			return -5
		parameter = ''
		if backend == 'cscope' and system:
			parameter = 'system'
		elif backend == 'gtags':
			parameter = label
		if verbose:
			sys.stdout.write('Buiding %s database for: %s\n'%(backend, root))
			sys.stdout.flush()
		if filelist:
			if filelist != '-' and (not os.path.exists(filelist)):
				errmsg('cannot read file list: ' + filelist, True)
				return -5
		es.generate(backend, parameter, update, filelist, verbose)
		return 0

	if operator in ('-F', '--find'):
		if not 'num' in options:
			errmsg('-num pattern required', True)
			return -6
		num = options['num']
		if num in (2, 5, 8, 9) and backend == 'gtags':
			errmsg('gtags does not support -%d pattern'%num, True)
			return -6
		name = options['name']
		es.find(backend, num, name)
		return 0

	if operator in ('-C', '--clean'):
		count = 30
		es.clean(count)
		return 0

	return 0


#----------------------------------------------------------------------
# testing case
#----------------------------------------------------------------------
if __name__ == '__main__':

	def test1():
		config = configure()
		config.init()
		print config.select('e:/lab/casuald/src/')
		print ''
		sys.stdout.flush()
		for hash, root, desc in config.list():
			print hash, root, desc['ctime']
		config.clear()
		#os.system('cmd /c start cmd')
		return 0

	def test2():
		sc = escope()
		sc.init()
		sc.select('e:/lab/casuald/src/')
		sc.gtags_generate(label = 'pygments', update = True, verbose = False)
		sys.stdout.flush()
		sc.find('gtags', 0, 'itm_send')
		return 0

	def test3():
		os.environ['ESCOPE'] = 'd:/temp/escope'
		sc = escope()
		os.chdir('e:/lab/casuald/src')
		sc.init()
		sc.select('e:/lab/casuald/src')
		sc.cscope_generate()
		sc.pycscope_generate()
		sc.cscope_find(3, 'itm_send')
		sc.pycscope_find(0, 'vimtool')

	def test4():
		main([__file__, '-h'])
		#main([__file__, '--version'])
		#main([__file__, '--clean'])
		return 0

	def test5():
		main([__file__, '-B', '-k', 'cscope', '-r', 'e:/lab/casuald'])
		main([__file__, '-F', '-k', 'cscope', '-r', 'e:/lab/casuald', '-2', 'itm_sendudp'])

	def test6():
		main([__file__, '-B', '-k', 'pycscope', '-r', 'e:/lab/casuald'])
		main([__file__, '-F', '-k', 'pycscope', '-r', 'e:/lab/casuald', '-2', 'plog'])

	def test7():
		main([__file__, '-B', '-k', 'gtags', '-r', 'e:/lab/casuald', '-l', 'pygments', '-v', '-u'])
		main([__file__, '-F', '-k', 'gtags', '-r', 'e:/lab/casuald', '-1', 'plog'])

	#test4()
	main()