Added VALID_HANDLER and some starting VALIDFUNCS to Evennia.

This commit is contained in:
Andrew Bastien 2019-04-10 01:54:17 -04:00
parent c9fdd8adca
commit 1b3331b495
4 changed files with 199 additions and 2 deletions

View file

@ -109,6 +109,7 @@ TASK_HANDLER = None
TICKER_HANDLER = None
MONITOR_HANDLER = None
CHANNEL_HANDLER = None
VALID_HANDLER = None
def _create_version():
@ -152,7 +153,7 @@ def _init():
global search_object, search_script, search_account, search_channel, search_help, search_tag, search_message
global create_object, create_script, create_account, create_channel, create_message, create_help_entry
global settings, lockfuncs, logger, utils, gametime, ansi, spawn, managers
global contrib, TICKER_HANDLER, MONITOR_HANDLER, SESSION_HANDLER, CHANNEL_HANDLER, TASK_HANDLER
global contrib, TICKER_HANDLER, MONITOR_HANDLER, SESSION_HANDLER, CHANNEL_HANDLER, TASK_HANDLER, VALID_HANDLER
global EvMenu, EvTable, EvForm, EvMore, EvEditor
global ANSIString
@ -213,6 +214,7 @@ def _init():
from .server.sessionhandler import SESSION_HANDLER
from .comms.channelhandler import CHANNEL_HANDLER
from .scripts.monitorhandler import MONITOR_HANDLER
from .utils.valid import VALID_HANDLER
# initialize the doc string
global __doc__

View file

@ -521,7 +521,7 @@ TIME_GAME_EPOCH = None
TIME_IGNORE_DOWNTIMES = False
######################################################################
# Inlinefunc & PrototypeFuncs
# Inlinefunc, PrototypeFuncs, and ValidFuncs
######################################################################
# Evennia supports inline function preprocessing. This allows users
# to supply inline calls on the form $func(arg, arg, ...) to do
@ -538,6 +538,10 @@ INLINEFUNC_MODULES = ["evennia.utils.inlinefuncs",
PROTOTYPEFUNC_MODULES = ["evennia.utils.prototypefuncs",
"server.conf.prototypefuncs"]
# Module holding validator functions. functions in later modules will
# override those in earlier ones.
VALIDFUNC_MODULES = ['evennia.utils.validfuncs', ]
######################################################################
# Default Account setup and access
######################################################################

16
evennia/utils/valid.py Normal file
View file

@ -0,0 +1,16 @@
from django.conf import settings
from evennia.utils.utils import callables_from_module
class ValidHandler(object):
def __init__(self):
self.valid_storage = {}
for module in settings.VALIDFUNC_MODULES:
self.valid_storage.update(callables_from_module(module))
def __getitem__(self, item):
return self.valid_storage.get(item, None)
VALID_HANDLER = ValidHandler()

175
evennia/utils/validfuncs.py Normal file
View file

@ -0,0 +1,175 @@
"""
Contains all the validation functions.
All validation functions must have a checker (probably a session) and entry arg.
They can employ more paramters at your leisure.
"""
import re as _re
import pytz as _pytz
import datetime as _dt
from django.core.exceptions import ValidationError as _error
from django.core.validators import validate_email as _val_email
from evennia.utils.ansi import ANSIString as _ansi
from evennia.utils.utils import string_partial_matching as _partial
_TZ_DICT = {str(tz): _pytz.timezone(tz) for tz in _pytz.common_timezones}
def color(entry, thing_name='Color'):
if not entry:
raise ValueError(f"Nothing entered for a {thing_name}!")
test_str = _ansi('|%s|n' % entry)
if len(test_str):
raise ValueError(f"'{entry}' is not a valid {thing_name}.")
return entry
def datetime(entry, thing_name='Datetime', from_tz=None):
"""
Args:
entry (str): A date string from a user.
thing_name (str): Name to display this datetime as.
from_tz (pytz): An instance of pytz from the user.
Returns:
datetime in utc.
"""
if not entry:
raise ValueError(f"No {thing_name} entered!")
if not from_tz:
from_tz = _pytz['UTC']
utc = _pytz['UTC']
now = _dt.datetime.utcnow().replace(tzinfo=utc)
cur_year = now.strftime('%Y')
split_time = entry.split(' ')
if len(split_time) == 3:
entry = f"{split_time[0]} {split_time[1]} {split_time[2]} {cur_year}"
elif len(split_time) == 4:
entry = f"{split_time[0]} {split_time[1]} {split_time[2]} {split_time[3]}"
else:
raise ValueError(f"{thing_name} must be entered in a 24-hour format such as: {now.strftime('%b %d %H:%H')}")
try:
local = _dt.datetime.strptime(input, '%b %d %H:%M %Y')
except ValueError:
raise ValueError(f"{thing_name} must be entered in a 24-hour format such as: {now.strftime('%b %d %H:%H')}")
local_tz = from_tz.localize(local)
return local_tz.astimezone(utc)
def duration(entry, thing_name='Duration'):
"""
Take a string and derive a datetime timedelta from it.
Args:
entry (string): This is a string from user-input. The intended format is, for example: "5d 2w 90s" for
'five days, two weeks, and ninety seconds.' Invalid sections are ignored.
thing_name (str): Name to display this query as.
Returns:
timedelta
"""
time_string = entry.split(" ")
seconds = 0
minutes = 0
hours = 0
days = 0
weeks = 0
for interval in time_string:
if _re.match(r'^[\d]+s$', interval.lower()):
seconds =+ int(interval.lower().rstrip("s"))
elif _re.match(r'^[\d]+m$', interval):
minutes =+ int(interval.lower().rstrip("m"))
elif _re.match(r'^[\d]+h$', interval):
hours =+ int(interval.lower().rstrip("h"))
elif _re.match(r'^[\d]+d$', interval):
days =+ int(interval.lower().rstrip("d"))
elif _re.match(r'^[\d]+w$', interval):
weeks =+ int(interval.lower().rstrip("w"))
elif _re.match(r'^[\d]+y$', interval):
days =+ int(interval.lower().rstrip("y")) * 365
else:
raise ValueError(f"Could not convert section '{interval}' to a {thing_name}.")
return _dt.timedelta(days, seconds, 0, 0, minutes, hours, weeks)
def future(entry, thing_name="Future Datetime", from_tz=None):
time = datetime(entry, thing_name)
if time < _dt.datetime.utcnow():
raise ValueError(f"That {thing_name} is in the past! Must give a Future datetime!")
return time
def signed_integer(entry, thing_name="Signed Integer"):
if not entry:
raise ValueError(f"Must enter a whole number for {thing_name}!")
try:
num = int(entry)
except ValueError:
raise ValueError(f"Could not convert '{entry}' to a whole number for {thing_name}!")
return num
def positive_integer(entry, thing_name="Positive Integer"):
num = signed_integer(entry, thing_name)
if not num >= 1:
raise ValueError(f"Must enter a whole number greater than 0 for {thing_name}!")
return num
def unsigned_integer(entry, thing_name="Unsigned Integer"):
num = signed_integer(entry, thing_name)
if not num >= 0:
raise ValueError(f"{thing_name} must be a whole number greater than or equal to 0!")
return num
def boolean(entry, thing_name="True/False"):
entry = entry.upper()
error = f"Must enter 0 (false) or 1 (true) for {thing_name}. Also accepts True, False, On, Off, Yes, No, Enabled, and Disabled"
if not entry:
raise ValueError(error)
if entry in ('1', 'TRUE', 'ON', 'ENABLED', 'ENABLE', 'YES'):
return True
if entry in ('0', 'FALSE', 'OFF', 'DISABLED', 'DISABLE', 'NO'):
return False
raise ValueError(error)
def timezone(entry, thing_name="Timezone"):
if not entry:
raise ValueError(f"No {thing_name} entered!")
found = _partial(_TZ_DICT.keys(), entry)
if found:
return _TZ_DICT[found]
raise ValueError(f"Could not find timezone '{entry}' for {thing_name}!")
def email(entry, thing_name="Email Address"):
try:
_val_email(entry) #offloading the hard work to Django!
except _error:
raise ValueError(f"That isn't a valid {thing_name}!")
return entry
def lockstring(entry, thing_name='lockstring', options=None):
entry = entry.strip()
if not entry:
raise ValueError(f"No {thing_name} entered to set!")
for locksetting in entry.split(';'):
access_type, lockfunc = locksetting.split(':', 1)
if not access_type:
raise ValueError("Must enter an access type!")
if options:
accmatch = _partial(options, access_type)
if not accmatch:
raise ValueError(f"Access type must be one of: {', '.join(options)}")
if not lockfunc:
raise ValueError("Lock func not entered.")
return entry