nakedmud-mod/
nakedmud-mod/html/tutorials/
nakedmud-mod/html/tutorials/building_extras/
nakedmud-mod/html/tutorials/c/
nakedmud-mod/html/tutorials/reference/
nakedmud-mod/html/tutorials/scripting/
nakedmud-mod/html/tutorials/scripting_extras/
nakedmud-mod/lib/
nakedmud-mod/lib/help/A/
nakedmud-mod/lib/help/B/
nakedmud-mod/lib/help/C/
nakedmud-mod/lib/help/D/
nakedmud-mod/lib/help/G/
nakedmud-mod/lib/help/H/
nakedmud-mod/lib/help/J/
nakedmud-mod/lib/help/L/
nakedmud-mod/lib/help/M/
nakedmud-mod/lib/help/O/
nakedmud-mod/lib/help/P/
nakedmud-mod/lib/help/R/
nakedmud-mod/lib/help/S/
nakedmud-mod/lib/help/W/
nakedmud-mod/lib/logs/
nakedmud-mod/lib/misc/
nakedmud-mod/lib/players/
nakedmud-mod/lib/pymodules/polc/
nakedmud-mod/lib/txt/
nakedmud-mod/lib/world/
nakedmud-mod/lib/world/zones/examples/
nakedmud-mod/lib/world/zones/examples/mproto/
nakedmud-mod/lib/world/zones/examples/oproto/
nakedmud-mod/lib/world/zones/examples/reset/
nakedmud-mod/lib/world/zones/examples/rproto/
nakedmud-mod/lib/world/zones/examples/trigger/
nakedmud-mod/lib/world/zones/limbo/
nakedmud-mod/lib/world/zones/limbo/room/
nakedmud-mod/lib/world/zones/limbo/rproto/
nakedmud-mod/src/alias/
nakedmud-mod/src/dyn_vars/
nakedmud-mod/src/editor/
nakedmud-mod/src/example_module/
nakedmud-mod/src/help2/
nakedmud-mod/src/set_val/
nakedmud-mod/src/socials/
nakedmud-mod/src/time/
################################################################################
#
# Four Worlds
# Copyright (c) 2009-????
#
# Package: polc
#    File: main.py
#
# The automatic Python OLC system, capable of generating an online content editor
# for a given Python class or dict by using dictionaries to discover properties.
#
# OLC Initialization and Rendering 
#
# Author: Stendec
#
################################################################################

import copy, olc, parsers, renderers
from types import *
from data import *

################################################################################
# A few things to let people skip 'import olc' in their own functions.
################################################################################
MENU_NOCHOICE = olc.MENU_NOCHOICE
MENU_CHOICE_INVALID = olc.MENU_CHOICE_INVALID

################################################################################
# OLC Initialization
################################################################################

def do_olc(sock, object, saver = None, key = None, options = None, autosave = False):
	'''Initialize the Python OLC system for the given object.'''
	# First, if there isn't a valid socket or object to edit, quit.
	if sock is None or object is None:
		return
	
	# Create a new data object for this editor.
	data = OLCData()
	
	# Store data into the data object.
	if saver is None:
		data.object		= object
	else:
		data.object		= copy.copy(object)
		data.saver		= saver
	data.key				= key
	data.sock				= sock

	# Make sure we have a proper options dict.'''
	if type(options) == dict: data.opts = options
	else: data.opts = { }
	if data.opts.has_key('autosave') and data.opts['autosave'] is True:
		autosave = True

	# Push our data through to the OLC menu.
	olc.do_olc(sock, polc_menu, polc_chooser, polc_parser, polc_saver, data, autosave)
	
################################################################################
# OLC Helper Functions
################################################################################

def fancy_key(key):
	'''Attempt to make a key name fancy. If it's in an array of default names, return the
		 corresponding fancy name. Otherwise, convert underscores to spaces and make it
		 title case.'''
	if fancy_keys.has_key(key): return fancy_keys[key]
	else: return key.replace('_',' ').title()

def get_name(object):
	'''Attempts to get a name from an object for display in the menu.'''
	if    type(object) == dict:
		if		object.has_key('name'):		return object['name']
		elif	object.has_key('title'):	return object['title']
		else:	return 'unknown'
	if		hasattr(object, '__name__'):	return object.__name__
	elif	hasattr(object, '__str__'):		return object.__str__()
	elif	hasattr(object, '__repr__'):	return object.__repr__().strip('<>')
	else:
		t = ''
		if		hasattr(object, '__class__'):
			t = str(object.__class__).split('.')
			if len(t) == 2: t = t[1]
			else: t = t[0]
			if t: t += ': '
		if		hasattr(object, 'name'):			return t+object.name
		elif	hasattr(object, 'title'):			return t+object.title
		else:																return t+"unknown"

def olc_enter(sock, data, key):
	'''Open an editor for a subobject with autosave set to True to prevent
		 showing the user a 'Save?' question when they back out.'''
	if type(data.object) == dict: object = data.object[key]
	else: object = getattr(data.object, key)
	opts = {}

	# Is there a name option?
	if not opts.has_key('name'):
		if data.opts.has_key('_'+key) and data.opts['_'+key].has_key('name'):
			opts['name'] = data.opts['_'+key]['name']
		else:
			opts['name'] = get_name(object)
		# No name? Use the key.
		if opts['name'] == 'unknown': opts['name'] = key.replace('_',' ').title()
	
	# Load options if there are any for this key.
	if data.opts.has_key('_'+key):
		for k,v in data.opts['_'+key].iteritems():
			opts[k] = v
	
	# Enter the OLC system.
	do_olc(sock, object, None, key, opts, True)

def notepad_enter(sock, data, key):
	'''Enter the notepad system. Store a copy of the working copy of the aux data
		 in the copy of the aux data attached to the socket. Confusing, no?'''
	sock.aux("olc").working		= data
	sock.aux("olc").key				= key
	t = data.k(key,'type') or 'text'
	
	if type(data.object) is dict: val = data.object[key]
	else: val = getattr(data.object, key)
	if not val.endswith('\r\n'): val += '\r\n'
	sock.edit_text(val, notepad_end, t)

def notepad_end(sock, text):
	'''Finish the notepad system.'''
	if text.endswith('\r\n'): text = text[:-2]
	if type(sock.aux("olc").working.object) is dict:
		sock.aux("olc").working.object[sock.aux("olc").key] = text
	else: setattr(sock.aux("olc").working.object, sock.aux("olc").key, text)
	sock.aux("olc").working		= None
	sock.aux("olc").key 			= None

################################################################################
# OLC Choice List Menu
################################################################################
def polc_choice_list(sock, data, key):
	'''Display a submenu of avaliable choices.'''
	data.ekey = key
	olc.do_olc(sock, polc_choice_menu, polc_choice_chooser, polc_choice_parser, None, data, True)

def polc_choice_menu(sock, data):
	k = data.ekey
	n = data.k(k,'name') or fancy_key(k)
	
	if type(data.object) == dict: val = data.object[k]
	else: val = getattr(data.object, k)
	
	# If it's a str, split the comma-separated values.
	if type(val) == str: val = val.replace(', ',',').split(',')
	
	sock.send("{nCurrent %s%s{n" % (n,renderers.render_choice_list(sock, data, k, val)))
	sock.send(renderers.render_choice_select(sock, data, k, val))

def polc_choice_chooser(sock, data, opt):
	try: opt = int(opt)
	except: return olc.MENU_CHOICE_INVALID

	k = data.ekey
	if type(data.object) == dict: val = data.object[k]
	else: val = getattr(data.object, k)
	
	# If it's a str, split the comma-separated values.
	is_str = type(val) == str
	if is_str: val = val.replace(', ',',').split(',')
	
	c = data.k(k,'choices')
	
	if type(c) == dict: ks = sorted(c.keys())
	else: ks = sorted(c)
	
	if opt < 0 or opt >= len(ks): return olc.MENU_CHOICE_INVALID
	v = ks[opt]
	if v in val:	val.remove(v)
	else:					val.append(v)
	
	val = sorted(val)
	if is_str: val = ', '.join(val)
	
	if type(data.object) is dict: data.object[k] = val
	else: setattr(data.object, k, val)
	return olc.MENU_NOCHOICE

def polc_choice_parser(sock, data, opt, val):
	return True

################################################################################
# OLC Menu Display
################################################################################

def polc_menu(sock, data):
	'''Display the menu for the provided object.'''
	# If we have an order option, use it. Else, use the raw keys
	if data.opts.has_key('order'): keys = data.opts['order']
	else:
		if type(data.object) == dict: keys = data.object.keys()
		else: keys = data.object.__dict__.keys()
		keys.sort(key=str.lower)
	
	# Output the name as the header.
	if data.opts.has_key('name'): n = data.opts['name']
	else:
		n = get_name(data.object)
		if n == 'unknown' and data.key is not None: n = data.key.replace('_',' ').title()
	sock.send("{y[{c%s{y]" % n)

	# Are any custom keys specified? If so, remove them from the list of keys to use.
	mkeys = list(menukeys)
	if data.opts.has_key('keys'):
		for k,v in data.opts['keys'].iteritems(): mkeys.remove(v)

	count = 0
	for k in keys:
		if not k: sock.send("")
		elif not data.opts.has_key('hidden') or (data.opts.has_key('hidden') and not k in data.opts['hidden']):
			hk = data.opts.has_key('_'+k)
			
			# Are we assuming a certain value?
			if hk and data.opts['_'+k].has_key('value'):
				val = data.opts['_'+k]['value']
			else:
				if type(data.object) == dict: val = data.object[k]
				else: val = getattr(data.object, k)
			
			# Are we forcing this to display a certain way?
			if hk and data.opts['_'+k].has_key('force'):
				if data.opts['_'+k]['force'] == list:
					# Convert to a list. For a string, separate by comma. Everything else, wrap it.
					if type(val) == str: val = val.replace(', ',',').split(',')
					else: val = [val]
				else:
					# Try converting it the messy way.
					try: val = data.opts['_k']['force'](val)
					except: val = "<ERROR>"
			
			# Get the name
			pk = hk and data.opts['_'+k].has_key('name')
			if pk: n = data.opts['_'+k]['name']
			else: n = fancy_key(k)
			
			# Generate a string from the value.
			# Is there a custom renderer?
			if		type(data.k(k,'renderer')) in (FunctionType,LambdaType,BuiltinFunctionType):
				n = '%-15s' % n
				v = ': {c' + data.k(k,'renderer')(sock, data, k, val)
			elif	type(data.k(k,'raw_renderer')) in (FunctionType,LambdaType,BuiltinFunctionType):
				v = data.k(k,'raw_renderer')(sock, data, k, val)
			elif	type(data.k(k,'extend_renderer')) in (FunctionType,LambdaType,BuiltinFunctionType):
				v = None
			
			# Choices have to be taken care of separately.
			elif	data.k(k,'choices') is not None:
				if	type(val) in (list,tuple):
					# List Display
					v = renderers.render_choice_list(sock, data, k, val)
				else:
					# Normal Display
					n = '%-15s' % n
					v = renderers.render_choice(sock, data, k, val)
			
			# Call a renderer, based on the type.
			# Boolean
			elif	type(val) is bool:
				n = '%-15s' % n
				v = renderers.render_bool(sock, data, k, val)
			
			# Number
			elif	type(val) in (int,float,long):
				n = '%-15s' % n
				v = renderers.render_number(sock, data, k, val)
			
			# List
			elif	type(val) in (list,tuple):
				v = renderers.render_list(sock, data, k, val)
			
			# String
			elif	type(val) in (str,unicode):
				v = renderers.render_string(sock, data, k, val)
			
			# Sub-Item
			elif	type(val) in (dict,ClassType,InstanceType) or data.k(k,'options') is not None:
				v = renderers.render_subitem(sock, data, k, val)
			
			# Last Effort
			else:
				if hasattr(val, '__str__'):	n,v = '%-15s' % n, ': {c' + str(val)
				else:												n,v = '%-15s' % n, ': {y<UNKNOWN>'
			
			# Display the option
			if	type(data.k(k,'extend_renderer')) in (FunctionType,LambdaType,BuiltinFunctionType):
				if data.opts.has_key('keys') and k in data.opts['keys']: sock.send_raw("{g%s) " % data.opts['keys'][k])
				else:
					sock.send_raw("{g%s) " % menukeys[count])
					count += 1
				data.k(k,'extend_renderer')(sock, data.object)
			
			elif data.opts.has_key('readonly') and k in data.opts['readonly']:
				sock.send("{g   %s%s{n" % (n,v))
			elif data.opts.has_key('keys') and k in data.opts['keys']:
				sock.send("{g%s) %s%s{n" % (data.opts['keys'][k], n, v))
			else:
				sock.send("{g%s) %s%s{n" % (menukeys[count], n, v))
				count += 1

def polc_chooser(sock, data, opt):
	'''Determine if the provided option was valid.'''
	# Is this a command for the character?
	if hasattr(sock, 'ch') and data.opts.has_key('allow-commands') and opt.startswith('- '):
		sock.ch.act(opt[2:])
		return olc.MENU_NOCHOICE
	
	# If we have an order option, use it. Else, use the raw keys
	if data.opts.has_key('order'):
		keys = copy.copy(data.opts['order'])
		while '' in keys: keys.remove('')
	else:
		if type(data.object) == dict: keys = data.object.keys()
		else: keys = data.object.__dict__.keys()
		keys.sort(key=str.lower)
	
	# Are any custom keys set? We have to build the list of potential keys specially in that case.
	menkeys = list(menukeys)
	if data.opts.has_key('keys'):
		for k,v in data.opts['keys'].iteritems(): menkeys.remove(v)
		mkeys = []
		lkeys = []
		count = 0
		ro = data.opts.has_key('readonly')
		hd = data.opts.has_key('hidden')
		hk = data.opts.has_key('keys')
		for k in keys:
			if		ro and k in data.opts['readonly']: continue
			elif	hd and k in data.opts['hidden']: continue
			elif	hk and k in data.opts['keys']: mkeys.append(data.opts['keys'][k].upper())
			else:
				mkeys.append(menkeys[count].upper())
				count += 1
			lkeys.append(k)
		menkeys = mkeys
		keys = lkeys
	else:
		lkeys = []
		ro = data.opts.has_key('readonly')
		hd = data.opts.has_key('hidden')
		for k in keys:
			if		ro and k in data.opts['readonly']: continue
			elif	hd and k in data.opts['hidden']: continue
			else: lkeys.append(k)
		keys = lkeys
	
	# It has to have a valid key.
	if not opt[0].upper() in menkeys:
		return olc.MENU_CHOICE_INVALID
	opt = menkeys.index(opt[0].upper())
	if opt < 0 or opt >= len(keys): return olc.MENU_CHOICE_INVALID
	
	# Get the item to determine what to do now.
	k = keys[opt]
	
	# Are we assuming a certain value?
	if data.opts.has_key('_'+k) and data.opts['_'+k].has_key('value'):
		val = data.k(k,'value')
	else:
		if type(data.object) == dict: val = data.object[k]
		else: val = getattr(data.object, k)

	# Are we forcing this to display a certain way?
	force = data.k(k,'force')
	if not force is None:
		if force == list:
			# Convert to a list. For a string, separate by comma. Everything else, wrap it.
			if type(val) == str: val = val.replace(', ',',').split(',')
			else: val = [val]
		else:
			# Try converting it the messy way.
			try: val = force(val)
			except: val = "<ERROR>"
	
	# Is there a custom handler?
	func		= data.k(k,'handler')
	if type(func) in (FunctionType,LambdaType,BuiltinFunctionType):
		return func(sock, data, k)
	
	# Is there an extension-type handler?
	func		= data.k(k,'extend_handler')
	if type(func) in (FunctionType,LambdaType,BuiltinFunctionType):
		v = func(sock, data.object)
		if v == olc.MENU_NOCHOICE: return v
		elif v == olc.MENU_CHOICE_INVALID: return v
		elif v == olc.MENU_CHOICE_OK: return opt + 1
	
	# Is there a custom prompt?
	prompt	= data.k(k,'prompt')
	n				= data.k(k,'name')
	if n is None: n = fancy_key(k)
	
	# Do we have a custom parser?
	func		= data.k(k,'parser')
	if type(func) in (FunctionType,LambdaType,BuiltinFunctionType):
		if prompt: sock.send_raw(prompt)
		else: sock.send_raw("Enter a new %s: " % n)
		return opt + 1
	
	# Is this a multiple-choice list?
	elif data.k(k,'choices'):
		if type(val) in (list, tuple):
			# The value itself is a list. Show the special choice list menu.
			polc_choice_list(sock, data, k)
			return olc.MENU_NOCHOICE
		else:
			sock.send(renderers.render_choice_select(sock, data, k, val))
			if prompt: sock.send_raw(prompt)
			else: sock.send_raw("Pick a %s: " %n)
			return opt + 1
	
	# Boolean Value, If so, just toggle it and return.
	elif type(val) is bool:
		if type(data.object) == dict: data.object[k] = not val
		else: setattr(data.object, k, not val)
		return olc.MENU_NOCHOICE
	
	# Numeric Value. Display a prompt.
	elif type(val) in (int, float, long):
		if prompt: sock.send_raw(prompt)
		else:
			min = data.k(k,'min')
			max = data.k(k,'max')
			range = data.k(k,'range')
			if type(range) == str: min, max = [int(value) for value in range.split(',')]
			elif type(range) in (list,tuple) and len(range) == 2: min, max = range[0], range[1]
			if min is not None and max is not None: sock.send_raw("{gEnter a new %s (%d-%d): " % (n,min,max))
			else: sock.send_raw("{gEnter a new %s: " % n)
		return opt + 1
	
	# List Value. Display a prompt.
	elif type(val) in (list, tuple):
		if prompt: sock.send_raw(prompt)
		else: sock.send_raw("{gEnter comma-separated list of %s: " % n)
		return opt + 1
	
	# String Value. If necessary, load the text editor. Else, display a prompt.
	elif type(val) in (str, unicode):
		if data.k(k,'multi'):
			notepad_enter(sock, data, k)
			return olc.MENU_NOCHOICE
		else:
			if prompt: sock.send_raw(prompt)
			else: sock.send_raw("{gEnter a new %s: " % n)
			return opt + 1
	
	# Sub-Item. Go into it.
	elif type(val) in (dict,ClassType,InstanceType):
		olc_enter(sock, data, k)
		return olc.MENU_NOCHOICE
	
	# Still here? You shouldn't be.
	return olc.MENU_CHOICE_INVALID

def polc_parser(sock, data, opt, val):
	'''Parse the user's input and validate it, then store it in the object.'''
	opt -= 1

	# If we have an order option, use it. Else, use the raw keys
	if data.opts.has_key('order'):
		keys = copy.copy(data.opts['order'])
		while '' in keys: keys.remove('')
	else:
		if type(data.object) == dict: keys = data.object.keys()
		else: keys = data.object.__dict__.keys()
		keys.sort(key=str.lower)
	
	# Are any custom keys set? We have to build the list of potential keys specially in that case.
	lkeys = []
	ro = data.opts.has_key('readonly')
	hd = data.opts.has_key('hidden')
	for k in keys:
		if		ro and k in data.opts['readonly']: continue
		elif	hd and k in data.opts['hidden']: continue
		else: lkeys.append(k)
	keys = lkeys

	# Is it out of our range?
	if opt < 0 or opt >= len(keys): return False
	
	# Get the item to determine what to do now.
	k = keys[opt]
	
	# Are we assuming a certain value?
	if data.hk(k,'value'): old = data.k(k,'value')
	else:
		if type(data.object) == dict: old = data.object[k]
		else: old = getattr(data.object, k)
	
	# Are we forcing this to display a certain way?
	force = data.k(k,'force')
	if not force is None:
		if force == list:
			# Convert to a list. For a string, separate by comma. Everything else, wrap it.
			if type(old) == str: old = old.replace(', ',',').split(',')
			else: old = [old]
		else:
			# Try converting it the messy way.
			try: old = force(old)
			except: old = "<ERROR>"

	# Do we have a custom parser?
	func = data.k(k,'parser')
	if type(func) in (FunctionType,LambdaType,BuiltinFunctionType):
		val = func(sock, data, k, val)
	
	# Do we have a custom extend-type parser?
	func = data.k(k,'extend_parser')
	if type(func) in (FunctionType,LambdaType,BuiltinFunctionType):
		val = func(sock, data.object, val)
		if val is True: return True
		else: return False
	
	# Multiple-choice List
	elif data.k(k,'choices'):							val = parsers.parse_choice(sock, data, k, val)
	
	# Call a function depending on the type of value.
	elif	type(old) in (int,float,long):	val = parsers.parse_number(sock, data, k, val)
	elif	type(old) in (list, tuple):		val = parsers.parse_list(sock, data, k, val)
	elif	type(old) in (str, unicode):		val = parsers.parse_string(sock, data, k, val)
	
	# Do we have a boolean? If so, return it.
	if type(val) is bool: return val
	
	# Is there a validator? If so, call it.
	func = data.k(k,'validate')
	if type(func) in (FunctionType,LambdaType,BuiltinFunctionType):
		if not func(sock, data, k, val): return False
	
	# It's okay. Store it.
	if type(data.object) is dict: data.object[k] = val
	else: setattr(data.object, k, val)
	return True

def polc_saver(data):
	'''Return the saved data to the saver function, if one exists.'''
	if data.saver:
		data.saver(data.sock, data.key, data.object)