Move olc to separate branch for now.

This is because it has increased in scope and would delay the release of
the devel branch unnecessarily.
This commit is contained in:
Griatch 2017-05-21 07:49:58 +02:00
parent 60fcb471ec
commit ce57b52dcf
5 changed files with 0 additions and 775 deletions

View file

@ -1,156 +0,0 @@
"""
OLC - On-Line Creation
This module is the core of the Evennia online creation helper system.
This is a resource intended for players with build privileges.
While the OLC command can be used to start the OLC "from the top", the
system is also intended to be plugged in to enhance existing build commands
with a more menu-like building style.
Functionality:
- Prototype management: Allows to create and edit Prototype
dictionaries. Can store such Prototypes on the Builder Player as an Attribute
or centrally on a central store that all builders can fetch prototypes from.
- Creates a new entity either from an existing prototype or by creating the
prototype on the fly for the sake of that single object (the prototype can
then also be saved for future use).
- Recording of session, for performing a series of recorded build actions in sequence.
Stored so as to be possible to reproduce.
- Export of objects created in recording mode to a batchcode file (Immortals only).
"""
from time import time
from collections import OrderedDict
from evennia.utils.evmenu import EvMenu
from evennia.commands.command import Command
# OLC settings
_SHOW_PROMPT = True # settings.OLC_SHOW_PROMPT
_DEFAULT_PROMPT = "" # settings.OLC_DEFAULT_PROMPT
_LEN_HISTORY = 10 # settings.OLC_HISTORY_LENGTH
# OLC Session
def _new_session():
"""
This generates an empty olcsession structure, which is used to hold state
information in the olc but which can also be pickled.
Returns:
olcsession (dict): An empty OLCSession.
"""
return {
# header info
"caller": None, # the current user of this session
"modified": time(),
"db_model": None, # currently unused, ObjectDB for now
"prompt_template": _DEFAULT_PROMPT, # prompt display
"olcfields": OrderedDict(), # registered OLCFields. Order matters
"prototype_key": "", # current active prototype key
}
def _update_prompt(osession):
"""
Update the OLC status prompt.
Returns:
prompt (str): The prompt based on the
prompt template, populated with
the olcsession state.
"""
return ""
def search_entity(osession, query):
"""
Perform a query for a specified entity. Which type of entity is determined by the osession
state.
Args:
query (str): This is a string, a #dbref or an extended search
"""
pass
def display_prototype(osession):
"""
Display prototype fields according to the order of the registered olcfields.
"""
# TODO: Simple one column display to begin with - make multi-column later
pkey = osession['prototype_key']
outtxt = ["=== {pkey} ===".format(pkey=pkey)]
for field in osession['olcfields'].values():
fname, flabel, fvalue = field.name, field.label, field.display()
outtxt.append(" {fieldname} ({label}): {value}".format(fieldname=fname,
label=flabel, value=fvalue))
return '\n'.join(outtxt)
def display_field_value(osession, fieldname):
"""
Display info about a specific field.
"""
field = osession['olcfields'].get(fieldname, None)
if field:
return "{fieldname}: {value}".format(fieldname=field.name, value=field.display())
# Access function
from evennia.utils.olc import olc_pages
def display_obj(obj):
"""
Test of displaying object using fields and pages.
"""
olcsession = _new_session()
olcsession['caller'] = obj
page = olc_pages.OLCObjectPage(olcsession)
obj.msg(str(page))
def OLC(caller, target=None, startnode=None):
"""
This function is a common entry-point into the OLC menu system. It is used
by Evennia systems to jump into the different possible start points of the
OLC menu tree depending on what info is already available.
Args:
caller (Object or Player): The one using the olc.
target (Object, optional): Object to operate on, if any is known.
startnode (str, optional): Where in the menu tree to start. If unset,
will be decided by whether target is given or not.
"""
startnode = startnode or (target and "node_edit_top") or "node_top"
EvMenu(caller, "evennia.utils.olc.olc_menu", startnode=startnode, target=target)
class CmdOLC(Command):
"""
Test OLC
Usage:
olc [target]
Starts the olc to create a new object or to modify an existing one.
"""
key = "olc"
def func(self):
OLC(self.caller, target=self.args)

View file

@ -1,523 +0,0 @@
"""
OLC fields describe how to edit and display a specific piece of data of a prototype within the OLC system.
The OLC system imports and adds these field classes to its prototype manipulation pages in order to
know what data to read and how to display it.
"""
from collections import deque
from evennia.utils.utils import to_str, to_unicode
from evennia.utils.olc import olc_utils
# from django.conf import settings
_OLC_VALIDATION_ERROR = """
Error storing data in {fieldname}:
{value}
The reported error was
{error}
"""
_LEN_HISTORY = 10 # settings.OLC_HISTORY_LENGTH
class InvalidActionError(RuntimeError):
"""
Raised when trying to perform a field action the field
does not support.
"""
pass
class ValidationError(RuntimeError):
"""
Raised when failing to validate new data being entered
into the field (from any source)
"""
pass
class OLCField(object):
"""
This is the parent for all OLC fields. This docstring acts
as the help text for the field.
"""
# name of this field, for error reporting
key = "Empty field"
# if this field must have a value different than None
required = False
# used for displaying extra info in the OLC
label = "Empty field"
# initial value of field if not given
default = None
# actions available on this field. Available actions
# are replace, edit, append, remove, clear, help
actions = ['edit', 'clear', 'help']
def __init__(self, olcsession):
self._value_history = deque([self.default], _LEN_HISTORY)
self._history_pos = 0
self._has_changed = False
def __str__(self):
return to_str(self.display())
def __unicode__(self):
return to_unicode(self.display())
# perform actions
# TODO - include editor in check!
def action_edit(self, *args):
"""
Edit field value.
Args:
newval (any): New value to add/replace with.
Raises:
InvalidActionError: If editing is not allowed.
"""
if args:
newval = args[0]
if 'edit' in self.actions:
self.value = newval
return
else:
newval = "<Not given>"
raise InvalidActionError('Edit {value}->{newval}'.format(value=self.value, newval=newval))
def action_clear(self, *args):
"""
Clear field back to default.
Returns:
default (any): The field'd default value, now set.
Raises:
InvalidActionError: If clearing is not allowed.
"""
if 'clear' in self.actions:
# don't validate this
object.__setattr__(self, 'value', self.default)
return self.value
raise InvalidActionError('Clear')
def action_help(self, *args):
"""
Get the help text for the field.
Returns:
help (str): Field help text.
Raises:
InvalidActionError: If help is not given for this field,
either becuase it's disallowed or unset.
"""
if 'help' not in self.actions or not self.__doc__:
raise InvalidActioNError('Help')
return self.__doc__
# storing data to the field in a history-aware way
@property
def value(self):
return self._value_history[self._history_pos]
@value.setter
def value(self, value):
"""
Update field value by updating the history.
"""
original_value = value
try:
value = self.validate(value)
except Exception as err:
errtxt = _OLC_VALIDATION_ERROR.format(fieldname=self.key, value=original_value, error=err)
raise ValidationError(errtxt)
if (self._value_history and isinstance(value, (basestring, bool, int, float)) and
self._value_history[0] == value):
# don't change/update history if re-adding the same thing
return
else:
self._has_changed = True
self._history_pos = 0
self._value_history.appendleft(value)
@value.deleter
def value(self):
self.history_pos = 0
self._value_history.appendleft(self.default)
def history(self, step):
"""
Change history position.
Args:
step (int): Step in the history stack. Positive movement
means moving futher back in history (with a maximum
of `settings.OLC_HISTORY_LENGTH`, negative steps
moves towards recent history (with 0 being the latest
value).
"""
self._history_pos = min(len(self.value_history)-1, max(0, self._history_pos + step))
def has_changed(self):
"""
Check if this field has changed.
Returns:
changed (bool): If the field changed or not.
"""
return bool(self._has_changed)
# overloadable methods
def from_entity(self, entity, **kwargs):
"""
Populate this field by retrieving data from an entity.
All fields on a page will have this called, so must
be able to handle also incompatible entities.
Args:
entity (any): An object to use for
populating this field (like an Object).
"""
pass
def to_prototype(self, prototype):
"""
Store this field value in a prototype.
Args:
prototype (dict): The prototype dict
to update with the value of this field.
"""
pass
def validate(self, value, **kwargs):
"""
Validate/preprocess incoming data to store in this field.
Args:
value (any): An input value to
validate
Kwargs:
any (any): Optional info to send to field.
Returns:
validated_value (any): The value, correctly
validated and/or processed to store in this field.
Raises:
ValidateException: If the field was given an
invalid value to validate.
"""
return str(value)
def display(self):
"""
How to display the field contents in the OLC display.
"""
return self.value
# OLCFields for all the standard model properties
# key, location, destination, home, aliases,
# permissions, tags, attributes ...
class OLCKeyField(OLCField):
"""
The name (key) of the object is its main identifier, used
throughout listings even if may not always be visible to
the end user.
"""
key = 'Name'
required = True
label = "The object's name"
def from_entity(self, entity, **kwargs):
self.value = entity.db_key
def to_prototype(self, prototype):
prototype['key'] = self.value
class OLCLocationField(OLCField):
"""
An object's location is usually a Room but could be any
other in-game entity. By convention, Rooms themselves have
a None location. Objects are otherwise only placed in a
None location to take them out of the game.
"""
key = 'Location'
required = False
label = "The object's current location"
def validate(self, value):
return olc_utils.search_by_string(self.olcsession, value)
def from_entity(self, entity, **kwargs):
self.value = entity.db_location
def to_prototype(self, prototype):
prototype['location'] = self.value
class OLCHomeField(OLCField):
"""
An object's home location acts as a fallback when various
extreme situations occur. An example is when a location is
deleted - all its content (except exits) are then not deleted
but are moved to each object's home location.
"""
key = 'Home'
required = True
label = "The object's home location"
def validate(self, value):
return olc_utils.search_by_string(self.olcsession, value)
def from_entity(self, entity, **kwargs):
self.value = entity.db_home
def to_prototype(self, prototype):
prototype['home'] = self.value
class OLCDestinationField(OLCField):
"""
An object's destination is usually not set unless the object
represents an exit between game locations. If set, the
destination should be set to the location you get to when
passing through this exit.
"""
key = 'Destination'
required = False
label = "The object's (usually exit's) destination"
def validate(self, value):
return olc_utils.search_by_string(self.olcsession, value)
def from_entity(self, entity, **kwargs):
self.value = entity.db_destination
def to_prototype(self, prototype):
prototype['destination'] = self.value
class OLCBatchField(OLCField):
"""
A field managing multiple values that can be appended to and
a given component popped out.
"""
actions = OLCField.actions + ['append', 'pop']
def action_append(self, value):
"""
Append a new value to this field.
Args:
value (any): The value to append.
"""
value = self.value
value.append(value)
self.value = value
def action_pop(self, index=-1):
"""
Pop an element from the field.
Args:
index (int, optional): Pop this index, otherwise pop the last
element in the field.
Returns:
element (any or None): The popped element or None.
"""
lst = self.value
try:
return lst.pop(int(index))
except IndexError:
return None
# setting single Alias
class OLCAliasField(OLCField):
key = "Alias"
required = False
label = "An alternative name for the object"
def from_entity(self, entity, **kwargs):
if "index" in kwargs:
self.value = entity.aliases.all()[int(kwargs)]
def to_prototype(self, prototype):
if is_iter(prototype["aliases"]):
prototype["aliases"].append(self.value)
else:
prototype["aliases"] = [self.value]
# batch-setting aliases
class OLCAliasBatchField(OLCBatchField):
"""
Specify as a comma-separated list. Use quotes around the
alias if the alias itself contains a comma.
Aliases are alternate names for an object. An alias is just
as fast to search for as a key and two objects are assumed
to have the same name is *either* their name or any of their
aliases match.
"""
key = 'Aliases'
required = False
label = "The object's alternative name or names"
def validate(self, value):
return olc_utils.split_by_comma(value)
def from_entity(self, entity, **kwargs):
self.value = list(entity.aliases.all())
def to_prototype(self, prototype):
prototype['aliases'] = self.value
# setting single Tag
class OLCTagField(OLCField):
"""
Specify as tagname or tagname:category
Tags are used to identify groups of objects for later quick retrieval.
This is very useful for anything from creating zones of rooms to
easily find all Characters belonging a given group etc. A tag can also
have a category for a second level of grouping.
"""
key = "Tag"
required = False
label = "A single label for the object."
def validate(self, value):
category = None
if ':' in value:
value, category = value.rsplit(':', 1)
return (value, category)
def from_entity(self, entity, **kwargs):
if "index" in kwargs:
self.value = entity.tags.all()[int(kwargs)]
def to_prototype(self, prototype):
if is_iter(prototype["tags"]):
prototype["tags"].append(self.value)
else:
prototype["tags"] = [self.value]
# batch-setting Tags
class OLCTagBatchField(OLCBatchField):
"""
Specify as a comma-separated list of tagname or tagname:category.
Tags are used to identify groups of objects for later quick retrieval.
This is very useful for anything from creating zones of rooms to
easily find all Characters belonging a given group etc.
"""
key = 'Tags'
required = False
label = "Attach labels to objects to group and find them."
def validate(self, value):
if isinstance(value, basestring):
return [tuple(tagstr.split(':', 1)) if ':' in tagstr else (tagstr, None)
for tagstr in olc_utils.split_by_comma(value)]
else:
# assume a list of (key, category) - just let it pass
return value
def from_entity(self, entity, **kwargs):
self.value = entity.tags.all(return_key_and_category=True)
def to_prototype(self, prototype):
prototype['tags'] = self.value
def display(self):
outstr = []
for key, category in self.value:
outstr.append("{key}:{category}".format(key=key, category=category))
return '\n'.join(outstr)
# TODO fix this to correctly handle key, value, category
# setting single Attribute
class OLCAttributeField(OLCField):
key = "Attribute"
required = False
label = "An alternative name for the object"
def from_entity(self, entity, **kwargs):
if "index" in kwargs:
self.value = entity.attributes.all()[int(kwargs)]
def to_prototype(self, prototype):
if is_iter(prototype["attrs"]):
prototype["attrs"].append(self.value)
else:
prototype["attrs"] = [self.value]
# batch-setting attributes
class OLCAttributeBatchField(OLCBatchField):
"""
Specify as a comma-separated list of attrname=value or attrname:category=value.
Attributes are arbitrary pieces of data attached to an object. They can
contain references to other objects as well as simple Python structures such
as lists and dicts.
"""
key = 'Attributes'
required = False
label = "Additional data attached to this object."
actions = OLCField.actions + ['append']
def validate(self, value):
if isinstance(value, basestring):
return [tuple(lhs.split(':', 1) + [rhs]) if ':' in lhs else (lhs, None) + (rhs, )
for lhs, rhs in (attrstr.split('=', 1) if ':' in attrstr else ((attrstr, None),))
for attrstr in olc_utils.split_by_comma(value)]
else:
# we assume this is a list of Attributes
return [(attr.key, attr.category, attr.value) for attr in value]
def from_entity(self, entity, **kwargs):
self.value = entity.attributes.all()
def to_prototype(self, prototype):
for key, category, value in self.value:
prototype['attrs'] = (key, value, category)
def display(self):
outstr = []
for key, category, value in self.value:
outstr.append("{key}:{category} = {value}".format(key=key, category=category, value=value))
return '\n'.join(outstr)

View file

@ -1,83 +0,0 @@
"""
This describes the menu structure/logic of the OLC system editor, using the EvMenu subsystem. The
various nodes are modular and will when possible make use of the various utilities of the OLC rather
than hard-coding things in each node.
Menu structure:
start:
new object
edit object <dbref>
manage prototypes
export session to batchcode file (immortals only)
new/edit object:
Protoype
Typeclass
Key
Location
Destination
PErmissions
LOcks
Attributes
TAgs
Scripts
create/update object
copy object
save prototype
save/delete object
update existing objects
manage prototypes
list prototype
search prototype
import prototype (from global store)
export session
"""
def node_top(caller, raw_input):
# top level node
# links to edit, manage, export
text = """OnLine Creation System"""
options = ({"key": ("|yN|new", "new", "n"),
"desc": "New object",
"goto": "node_new_top",
"exec": _obj_to_prototype},
{"key": ("|yE|ndit", "edit", "e", "m"),
"desc": "Edit existing object",
"goto": "node_edit_top",
"exec": _obj_to_prototype},
{"key": ("|yP|nrototype", "prototype", "manage", "p", "m"),
"desc": "Manage prototypes",
"goto": "node_prototype_top"},
{"key": ("E|yx|nport", "export", "x"),
"desc": "Export to prototypes",
"goto": "node_prototype_top"},
{"key": ("|yQ|nuit", "quit", "q"),
"desc": "Quit OLC",
"goto": "node_quit"},)
return text, options
def node_quit(caller, raw_input):
return 'Exiting.', None
def node_new_top(caller, raw_input):
pass
def node_edit_top(caller, raw_input):
# edit top level
text = """Edit object"""
def node_prototype_top(caller, raw_input):
# manage prototypes
pass
def node_export_top(caller, raw_input):
# export top level
pass

View file

@ -1,13 +0,0 @@
"""
Miscellaneous utilities for the OLC system.
"""
import csv
def search_by_string(olcsession, query):
pass
def split_by_comma(string):
return csv.reader([string], skipinitialspace=True)