#!/usr/bin/python

# REQUIREMENTS:
#	Python 2.6.x
#	requests module
#
#	yum -y install ipython python-argparse python-requests \
#          --enablerepo=epel

# We do NOT have a way of automatically linking these lists to AD
#     waiting on SSL certificate from I*
#     and the AD gods have to delete/recreate groups that have been linked to 
#     AD using this method anyhow
# We do NOT have a way of automatically assigning of unique GIDs to anything
#
# TODO:
# perpetual cleanup
# --quiet, --verbose

# v0.41 30-Sep-2013 - Add username-only result mode, reads amtools.conf
# v0.4  20-Jun-2013 - Added toggling of 'manager' and 'deliver to user' flags.
# v0.3  12-Jun-2013 - Feature request: query list owner/managers


# so we can include newlines in the argparse description
from argparse import RawTextHelpFormatter 
import getpass, sys, pprint, requests, json, argparse, re
import ConfigParser

from IPython import embed # just for debugging


ENC = 'json' # preferred encoding
ML_REAL_SSL_CERT = False
ML_BASE_URL = 'https://rest.maillist.sfu.ca'
ML_OPS_URL = ML_BASE_URL + '/maillists.' + ENC
AUTH_BASE_URL = ML_BASE_URL + '/authenticationtoken/'
POST_HEADERS = {'Content-type': 'application/json', 'Accept': 'text/plain'}

config = ConfigParser.RawConfigParser()
config.read('/exports/nfs4tools/amtools.conf')

ADUSER = config.get('default','default_ldap_user')
ADPASS = config.get('default','default_ldap_password')

# sample amtools.conf:
# default_ldap_user     = foo
# default_ldap_password = bar
# .. in the event you have a legitimate reason for storing credentials on disk




def GetAuthToken(auth_url):
	'''Connect to SFU REST server and obtain an auth token. This method 
		of getting a token is deprecated but it's the only one I could 
		figure out. <cough>

		https://rest.maillist.sfu.ca/authenticationtoken/
	'''
		
	#print("Getting auth token...")
	r = requests.get(auth_url, verify=ML_REAL_SSL_CERT)
	#print(r.status_code)

	# expected result:
	# u'{"id":"jsixpack",
	# 	"type":"authenticationtoken",
	# 	"username":"jsixpack",
	# 	"token":"MLTOKEN_BZiMS1b9YxAwoqGNKeGHQlBdE80TKVpLos8F58pFVYsPsxerWxI9pLefuXwd0LF5"}'

	if r.status_code == 200:
		# all is well, look for auth token
		chunks = json.loads(r.text)
		auth_token = chunks['token'].encode('ascii')
		#sys.stderr.write("Token: " + auth_token + "\n")
		return(auth_token)
	else:
		sys.stderr.write('FATAL: ' + r.text + '\n')
		exit(1)
	return





def CreateMaillist(auth_token, maillist_name, maillist_desc):
	''' Create a new mailing list. Feed in RCG default attributes
		and the required maillist name and description.

		https://rest.maillist.sfu.ca/maillists.json?name=xxx&desc=yyy&sfu_token=...
	'''
	create_qs = { # qs = query string
		'sfu_token': auth_token,
		'name': maillist_name,
		'desc': maillist_desc,
		'localSubscriptionPolicyCodeString': 'OWNERONLY',
		'externalSubscriptionPolicyCodeString': 'OWNERONLY',
		'externalSenderPolicyCodeString': 'RESTRICTED',
		'localSenderPolicyCodeString': 'RESTRICTED',
		'disableUnsubscribe': 'true',
		'externalDefaultAllowedToSend': 'false',
	  }
	pprint.pprint(create_qs)

	sys.stderr.write("Creating maillist ... ")
 	r = requests.post(ML_OPS_URL, params=create_qs, verify=ML_REAL_SSL_CERT)

	sys.stderr.write(str(r.status_code) + "\n")

	if r.status_code == 201:
		sys.stderr.write("List creation successful\n")
		results = json.loads(r.text)
		sys.stderr.write(maillist_name + ':' + str(results['id']) + "\n")
		return(results['id'])
	elif r.status_code == 409: # already exists
		sys.stderr.write('WARNING: ' + r.text + "\n")
		return
	else:
		sys.stderr.write('FATAL: ' + r.text + "\n")
		exit(1)
	return





def GetMaillistIDNum(auth_token, maillist_name):
	'''
	IN: Maillist name.
	OUT: Corresponding maillist ID number.

	https://rest.maillist.sfu.ca/maillists{.enc}?name=xxx&sfu_token=...
	'''
	getid_qs = { # query string
		'sfu_token': auth_token,
		'name': maillist_name,
	}
	#sys.stderr.write("Fetching list ID number ... ")
 	r = requests.get(ML_OPS_URL, params=getid_qs, verify=ML_REAL_SSL_CERT)
	#sys.stderr.write(str(r.status_code) + "\n")

	if r.status_code == 200:
		results = json.loads(r.text) # huge dict of useful information
		#print(maillist_name + ':' + str(results['id']))
		return(results['id']) # but we just return the id number
	else:
		print('FATAL: ' + r.text)
		exit(1)
	return





def DumpRelationships(auth_token, maillist_idnum):
	'''Dump a maillist's membership in dict format, e.g.: 

	https://rest.maillist.sfu.ca/maillists/ml_id_###/members.json?sfu_token=...

	[{"id":11311945472,"type":3,"address":"Joseph Sixpack
	<jsixpack@sfu.ca>","autoGenerated":false,"canonicalAddress":"jsixpack","
	copyOnSend":true,"deliver":true,"instructor":false,"manager":false,"
	pending":false,"subscribedDate":"2013-05-16T10:14:26Z","username":"
	jsixpack","uri":"https://rest.maillist.sfu.ca/members/11311945472.json","
	listUri":"https://rest.maillist.sfu.ca/maillists/33435.json","
	memberListUri":null,"isAllowedToSend":true},

	{...},]
	
	Note the "uri" field. Every maillist<=>person relationship is assigned
	a specific ID number. No, the API will NOT resolve or look that up for
	you. (YOU're a computer, why don't YOU do it? sheesh) You'll need this
	number to unsubscribe members.
	'''

	ML_MEMBERINFO_URL = ML_BASE_URL + '/maillists/' + str(maillist_idnum) + '/members.' + ENC

	memberinfo_qs = { # query string
		'sfu_token': auth_token,
	}
#	sys.stderr.write("Fetching maillist membership ...")
	r = requests.get(ML_MEMBERINFO_URL, params=memberinfo_qs, verify=ML_REAL_SSL_CERT)
#	sys.stderr.write(str(r.status_code) + "\n")

	if r.status_code != 200:
		sys.stderr.write(' FATAL: ' + str(r.status_code) + ': ' + r.text + "\n")
		exit(1)
	return(json.loads(r.text))





def DumpMaillistMembers(auth_token, maillist_name, maillist_idnum):
	'''Return a list of mailing list members in this format:
		Joseph Sixpack <joe@sfu.ca>
		Sally Q. Public <sqpublic@sfu.ca>
		...

	https://rest.maillist.sfu.ca/maillists/ml_id_###/members.json?sfu_token=...
	'''
	listmembers = DumpRelationships(auth_token, maillist_idnum)

	for member in listmembers:
		try:
			print(member['address'])
		except KeyError: # maillists contained in maillists don't have usernames
			print(member['canonicalAddress']) # but they do have an address		
	return



def DumpMaillistUsernames(auth_token, maillist_name, maillist_idnum, cmd):
	'''Return a list of mailing list members in this format:
		joe
		sqpublic
		...

	https://rest.maillist.sfu.ca/maillists/ml_id_###/members.json?sfu_token=...
	'''
	listmembers = DumpRelationships(auth_token, maillist_idnum)


	for member in listmembers:
		try:
			print(member['username'])
		except KeyError: # maillists contained in maillists don't have usernames
			if cmd['exclude_nested_list_names'] != True:
				print(member['canonicalAddress']) # but they do have an address
					# which we might not want to print if the user requested
					# just usernames, not usernames+maillist names
	return





def DelMembersFromList(auth_token, maillist_name, maillist_idnum, unixids):
	'''Remove one or more members of a mailing list.

	https://rest.maillist.sfu.ca/maillists/members/maillist<=>subscriber_relationship_id###.json
	'''
	unixids_list = []

	# if we are passed one user, we receive a string
	# if we are passed several users, we receive a list
	# this is a cheap way of ensuring that we parse 'jdoe' as 'jdoe'
	# and not 'j', 'd', 'o', 'e'
	if isinstance(unixids, str):
		unixids_list.append(unixids)
	else:
		unixids_list = unixids

	listmembers = DumpRelationships(auth_token, maillist_idnum)

	print("\nRemoving users from " + maillist_name)
	# passing multiple members as a list of dicts is unsupported
	# or very poorly documented, so feed users in one at a time
	for unixid in unixids_list:
		# fetch the maillist ID record number
		# for *this* person's subscription to *this* maillist
		# 
		# select a particular dict by value from a list of dicts:
		# http://stackoverflow.com/a/8653568
		try:
			subscr_id = (item for item in listmembers if item['canonicalAddress'] == unixid).next()['id']
		except StopIteration:
			print("There are no members in this maillist.")

		ml_del_member_url = ML_BASE_URL + '/members/' + str(subscr_id) + '.' + ENC

		req_attribs = {
			'sfu_token': auth_token,
			'id': subscr_id,
		}
		sys.stderr.write(unixid + ":" + str(subscr_id) + " | " + maillist_name + " ...")
		r = requests.delete(ml_del_member_url, params=req_attribs, verify=ML_REAL_SSL_CERT)
		sys.stderr.write(str(r.status_code))

		if r.status_code == 204: # already exists
			sys.stderr.write(" removed\n")
		elif r.status_code == 404:
			sys.stderr.write(" was not subscribed\n")
		else:
			sys.stderr.write(' FATAL: ' + str(r.status_code) + ': ' + r.text + "\n")
			exit(1)
	return

	
def AddMembersToList(auth_token, maillist_name, maillist_idnum, 
					unixids, manager_flag, deliver_flag):
	'''Add one or more subscribers to a mailing list.
	
	https://rest.maillist.sfu.ca/maillists/ml_id_###/members.json
	'''
	ML_MEMBERS_URL = ML_BASE_URL + '/maillists/' + str(maillist_idnum) + '/members.' + ENC

	unixids_list = []

	# if we are passed one user, we receive a string
	# if we are passed several users, we receive a list
	# this is a cheap way of ensuring that we parse 'jdoe' as 'jdoe'
	# and not 'j', 'd', 'o', 'e'
	if isinstance(unixids, str):
		unixids_list.append(unixids)
	else:
		unixids_list = unixids

	# passing multiple members as a list of dicts is unsupported
	# or very poorly documented, so feed users in one at a time
	for unixid in unixids_list:
		if not re.match(r'@', unixid):
			unixid += '@sfu.ca' # print('Assuming ' + unixid + ' is @sfu.ca')
		req_attribs = {
				'sfu_token': auth_token,
				'address': unixid,
			}


		sys.stderr.write(unixid + " => " + maillist_name + " ... ")
		r = requests.post(ML_MEMBERS_URL, params=req_attribs, verify=ML_REAL_SSL_CERT)
		sys.stderr.write(str(r.status_code))

		if r.status_code == 201: # already exists
			sys.stderr.write(" OK")
		elif r.status_code == 400: # already exists
			sys.stderr.write(" WARNING: no such active user")
		elif r.status_code == 409: # already exists
			sys.stderr.write(" already a member")
		else:
			sys.stderr.write(' FATAL: ' + str(r.status_code) + ': ' + r.text + "")
			exit(1)

		# full user record looks like this:
		# {u'address': u'Joseph Sixpack <jsixpack@sfu.ca>',
		#  u'autoGenerated': False,
		#  u'canonicalAddress': u'jsixpack',
		#  u'copyOnSend': True,
		#  u'deliver': True,
		#  u'id': 11416235356666,
		#  u'instructor': False,
		#  u'isAllowedToSend': True,
		#  u'listUri': u'https://rest.maillist.sfu.ca/maillists/852464691.json',
		#  u'manager': False,
		#  u'memberListUri': None,
		#  u'pending': False,
		#  u'subscribedDate': u'2013-06-19T09:29:49Z',
		#  u'type': 3,
		#  u'uri': u'https://rest.maillist.sfu.ca/members/11416235356666.json',
		#  u'username': u'jsixpack'}

		# fetch user record ID number
		ML_GET_MEMBERIDNUM_URL = ML_BASE_URL + '/maillists/' + str(maillist_idnum) + \
		    '/members.' + ENC + '?member=' + unixid
		r = requests.get(ML_GET_MEMBERIDNUM_URL, params=req_attribs, verify=ML_REAL_SSL_CERT)
		members_raw = json.loads(r.text)
		member_id_num = members_raw['id']

		# with member ID number in hand, update member record
		# with desired manager/deliver flags
		# I hope
		ML_MEMBER_RESOURCE_URL = ML_BASE_URL + '/members/' + str(member_id_num) + '.' + ENC

		# if the user has requested we set the manager or deliver flag
		# then add those to the attributes we're going to POST
		# and display feedback
		if manager_flag != None:
			req_attribs['manager'] = manager_flag
			sys.stderr.write(" | manager:" + str(manager_flag) )

		if deliver_flag != None:
			req_attribs['deliver'] = deliver_flag
			sys.stderr.write(" | deliver:" + str(deliver_flag) )

		#pprint.pprint(req_attribs)

		r = requests.put(ML_MEMBER_RESOURCE_URL, params=req_attribs, verify=ML_REAL_SSL_CERT,
		       data=json.dumps(req_attribs), headers=POST_HEADERS)

		sys.stderr.write(" ... " + str(r.status_code) + "\n")
	return






def DestroyMaillist(auth_token, maillist_name, maillist_idnum):
	'''Destroy a mailing list.
	
	https://rest.maillist.sfu.ca/maillists/ml_id_###.json
	'''

	ML_KILL_LIST_URL = ML_BASE_URL + '/maillists/' + str(maillist_idnum) + '.' + ENC

	req_attribs = {
		'sfu_token': auth_token,
	}

	sys.stderr.write("Destroying maillist " + maillist_name + " ... ")
	r = requests.delete(ML_KILL_LIST_URL, params=req_attribs, verify=ML_REAL_SSL_CERT)
	sys.stderr.write(str(r.status_code))

	if r.status_code == 204:
		sys.stderr.write(" OK\n")
	else:
		sys.stderr.write(" FATAL: " + str(r.status_code) + ': ' + r.text + "\n")
		exit(1)
	return


def DumpMaillistParameters(auth_token, maillist, maillist_idnum):
	ML_PARAMS_URL = ML_BASE_URL + '/maillists/' + '.' + ENC + '?name=' + maillist

	params_qs = { # query string
		'sfu_token': auth_token,
	}
#	sys.stderr.write("Fetching maillist parameters ...")
	r = requests.get(ML_PARAMS_URL, params=params_qs, verify=ML_REAL_SSL_CERT)
#	sys.stderr.write(str(r.status_code) + "\n")

	if r.status_code != 200:
		sys.stderr.write(' FATAL: ' + str(r.status_code) + ': ' + r.text + "\n")
		exit(1)
	return(json.loads(r.text))


def DumpMaillistExecutives(auth_token, maillist, maillist_idnum):
	ML_PARAMS_URL = ML_BASE_URL + '/maillists/' + '.' + ENC + '?name=' + maillist

	params_qs = { # query string
		'sfu_token': auth_token,
	}
	sys.stdout.write(maillist + '|owner:')
	r = requests.get(ML_PARAMS_URL, params=params_qs, verify=ML_REAL_SSL_CERT)

	if r.status_code != 200:
		sys.stderr.write('\nFATAL: ' + str(r.status_code) + ': ' + r.text + "\n")
		exit(1)
	ml_params = (json.loads(r.text))
        sys.stdout.write(ml_params['owner'] + '\n')

	sys.stdout.write(maillist + '|managers:')
        ml_managers_url = ml_params['managersUri']
	r = requests.get(ml_managers_url, params=params_qs, verify=ML_REAL_SSL_CERT)
	if r.status_code != 200:
		sys.stderr.write(str(r.status_code) + ': ' + r.text + "\n")
		exit(1)
	else:
		managers_list = []
		managers_raw = json.loads(r.text)
		for manager in managers_raw:
			managers_list.append(manager['canonicalAddress'])
		sys.stdout.write(','.join(managers_list) + '\n')
	return


def DumpMaillistStatus(auth_token, maillist, maillist_idnum):
	ML_PARAMS_URL = ML_BASE_URL + '/maillists/' + '.' + ENC + '?name=' + maillist

	params_qs = { # query string
		'sfu_token': auth_token,
	}
	sys.stdout.write(maillist + ": ")
	r = requests.get(ML_PARAMS_URL, params=params_qs, verify=ML_REAL_SSL_CERT)

	if r.status_code != 200:
		sys.stderr.write(' FATAL: ' + str(r.status_code) + ': ' + r.text + "\n")
		exit(1)
	else:
		sys.stdout.write(json.loads(r.text)['status'] + "\n")
	return
	



	
def main(argv):
	parser = argparse.ArgumentParser(
		prog='smac',
		formatter_class = RawTextHelpFormatter,
		description="Simple Maillist API Client\n\
	[-c]reate a new mailing list\n\
	[-Q]uery the membership of a mailing list\n\
	[-k]ill/destroy a mailing list",
		epilog='The Maillist REST API is still in flux. So is this tool. You have been warned.'
	)

	parser.add_argument('-V', '--version',
		action='version',
		version='SMAC 0.4, 20-Jun-2013')

	parser.add_argument('-u', '--mluser', dest='mluser', 
		action='store',	metavar='username', required=False,
		default=ADUSER,
		help='SFU Maillist Manager username')

	parser.add_argument('-p', '--mlpass', dest='mlpass', 
		action='store',	metavar='password',	required=False,
		default=ADPASS,
		help='SFU Maillist Manager password')

	parser.add_argument('-n', '--listname', dest='listname', 
		action='store',	metavar='sfu-funlist', required=True, nargs='+',
		help='Name of maillists to create, delete or otherwise manipulate')

	parser.add_argument('-d', '--listdesc', dest='listdesc',
		action='store',	metavar='description',
		help='When creating list: maillist description enclosed in quotation marks')

	parser.add_argument('-A', dest='unixids_to_add',
		action='store',	metavar='jsixpack', nargs='+',
		help='Subscribe SFU computing IDs to a maillist')

	parser.add_argument('-E', '--om', dest='om_mode', 
		action='store_true', 
		help='List the Owners and Managers of this list ([E]xecutives? No? Okay, I\'ll leave)')

	parser.add_argument('-e', '--exclude-nested', dest='exclude_nested_list_names', 
		action='store_true', default=True,
		help='When generating a member list, exclude the names of other mailing lists (i.e., usernames only)')

	parser.add_argument('-T', '--status', dest='get_status', 
		action='store_true', 
		help='Report whether a maillist is active or inactive')

	parser.add_argument('-U', '--shortusernames', dest='short_usernames', 
		action='store_true', 
		default=False,
		help='Return short usernames, one per line, instead of the default RFC 5321 To: format')

	parser.add_argument('-P', '--params', dest='get_params', 
		action='store_true', 
		help='Dump maillist parameters')

	parser.add_argument('-R', '--rawmembers', dest='get_rawmembers', 
		action='store_true', 
		help='Dump maillist member JSON')

	parser.add_argument('-M', dest='manager_flag_endstate',
		action='store', metavar='on|off',
		help='Set "manager" flag for users specified with -A')

	parser.add_argument('-D', dest='deliver_flag_endstate',
		action='store', metavar='on|off',
		help='Set "deliver to user" flag for users specified with -A')

	# not wired to anything yet
	parser.add_argument('-q', '--quiet', '--silent', dest='quiet_mode', 
		action='store_true',
		help='shut up')

	# not wired to anything yet
	parser.add_argument('-v', '--verbose', '--debug', dest='verbose_mode', 
		action='store_true', 
		help='tell me more')

	parser.add_argument('-Q', dest='query_mode',
		action='store_true',
		help='query mode: show members of a maillist')

	parser.add_argument('-k', dest='kill_mode',
		action='store_true',
		help='kill mode: delete a maillist')

	arg_group = parser.add_mutually_exclusive_group()
	arg_group.add_argument('-c', dest='create_mode', 
		action='store_true',
		help='create mode: create a new maillist')

	arg_group.add_argument('-X', dest='unixids_to_del', 
		action='store',	metavar='jsixpack', nargs='+',
		help='remove SFU computing IDs from a maillist')

	if len(sys.argv) <= 1:
		parser.print_help()
		exit(1)

	# spring into action
	cmd = vars(parser.parse_args())
	#pprint.pprint(parser.parse_args())

	# if -p/--mlpass is empty or not specified at all, prompt for a password
	try:
		if len(cmd['mlpass']) > 0:
			pass
	except TypeError:
		cmd['mlpass'] = getpass.getpass(cmd['mluser'] + ' password: ')


	auth_url = AUTH_BASE_URL + cmd['mluser'] + '.' + ENC + '?password=' + cmd['mlpass']
	auth_token = GetAuthToken(auth_url)



	# iterate over the mailing lists provided
	maillists_to_process = []

	# if we are passed one user, we receive a string
	# if we are passed several users, we receive a list
	# this is a cheap way of ensuring that we parse 'jdoe' as 'jdoe'
	# and not 'j', 'd', 'o', 'e'
	if isinstance(cmd['listname'], str):
		maillists_to_process.append(unixids)
	else:
		maillists_to_process = cmd['listname']

	# take -M(anager status) and -D(eliver status)
        # and convert 1/on/yes/true to True
        # and 0/off/no/false to False
	# but only if -M/-D have been specified
	try:
		cmd['manager_flag_endstate']
	except NameError:
		print("-M not specified")
	else:
		# I sure wish Python had a case statement
		if (cmd['manager_flag_endstate'] == '1' or
                    cmd['manager_flag_endstate'] == 'on' or
                    cmd['manager_flag_endstate'] == 'yes' or
                    cmd['manager_flag_endstate'] == 'true'):
			cmd['manager_flag_endstate'] = True
		if (cmd['manager_flag_endstate'] == '0' or
                    cmd['manager_flag_endstate'] == 'off' or
                    cmd['manager_flag_endstate'] == 'no' or
                    cmd['manager_flag_endstate'] == 'false'):
			cmd['manager_flag_endstate'] = False

	try:
		cmd['deliver_flag_endstate']
	except NameError:
		print("-D not specified")
	else:
		if (cmd['deliver_flag_endstate'] == '1' or
                    cmd['deliver_flag_endstate'] == 'on' or
                    cmd['deliver_flag_endstate'] == 'yes' or
                    cmd['deliver_flag_endstate'] == 'true'):
			cmd['deliver_flag_endstate'] = True
		if (cmd['deliver_flag_endstate'] == '0' or
                    cmd['deliver_flag_endstate'] == 'off' or
                    cmd['deliver_flag_endstate'] == 'no' or
                    cmd['deliver_flag_endstate'] == 'false'):
			cmd['deliver_flag_endstate'] = False


	for maillist in maillists_to_process:
		if cmd['create_mode'] == False:
			maillist_idnum = GetMaillistIDNum(auth_token, maillist)
		else:
			CreateMaillist(auth_token, maillist, cmd['listdesc'])
			maillist_idnum = GetMaillistIDNum(auth_token, maillist)

		if cmd['kill_mode'] == True:
			DestroyMaillist(auth_token, maillist, maillist_idnum)

		if cmd['query_mode'] == True:
			if cmd['short_usernames'] == True:
				DumpMaillistUsernames(auth_token, maillist, maillist_idnum, cmd)
			else:
				DumpMaillistMembers(auth_token, maillist, maillist_idnum)

		if cmd['short_usernames'] == True:
			DumpMaillistUsernames(auth_token, maillist, maillist_idnum, cmd)
		


		try:
			if len(cmd['unixids_to_add']) > 0:
				AddMembersToList(auth_token, maillist, maillist_idnum, 
					cmd['unixids_to_add'],
					manager_flag=cmd['manager_flag_endstate'], 
					deliver_flag=cmd['deliver_flag_endstate'])
		except TypeError:
			pass

		try:
			if len(cmd['unixids_to_del']) > 0:
				DelMembersFromList(auth_token, maillist, maillist_idnum, cmd['unixids_to_del'])
		except TypeError:
			pass

		if cmd['get_params'] == True:
			pprint.pprint(DumpMaillistParameters(auth_token, maillist, maillist_idnum))

		if cmd['om_mode'] == True:
			DumpMaillistExecutives(auth_token, maillist, maillist_idnum)


		if cmd['get_rawmembers'] == True:
			pprint.pprint(DumpRelationships(auth_token, maillist_idnum))


		if cmd['get_status'] == True:
			DumpMaillistStatus(auth_token, maillist, maillist_idnum)



if __name__ == "__main__":
	main(sys.argv[1:])
