From 6fa280b9fdcee87835674242866ab13208803552 Mon Sep 17 00:00:00 2001 From: Ryan Stein Date: Sun, 29 Oct 2017 13:40:30 -0400 Subject: [PATCH 001/493] Run 2to3. --- bin/project_rename.py | 24 +- evennia/__init__.py | 4 +- evennia/accounts/accounts.py | 4 +- evennia/accounts/bots.py | 2 +- evennia/accounts/manager.py | 4 +- evennia/accounts/migrations/0001_initial.py | 2 +- .../accounts/migrations/0002_move_defaults.py | 2 +- .../migrations/0003_auto_20150209_2234.py | 2 +- .../migrations/0004_auto_20150403_2339.py | 2 +- .../migrations/0005_auto_20160905_0902.py | 2 +- .../migrations/0006_auto_20170606_1731.py | 2 +- .../migrations/0007_copy_player_to_account.py | 2 +- evennia/accounts/models.py | 2 +- evennia/commands/cmdhandler.py | 8 +- evennia/commands/cmdparser.py | 4 +- evennia/commands/cmdsethandler.py | 8 +- evennia/commands/command.py | 4 +- evennia/commands/default/account.py | 4 +- evennia/commands/default/building.py | 8 +- evennia/commands/default/comms.py | 2 +- evennia/commands/default/system.py | 14 +- evennia/commands/default/tests.py | 8 +- evennia/comms/channelhandler.py | 4 +- evennia/comms/comms.py | 2 +- evennia/comms/managers.py | 8 +- evennia/comms/migrations/0001_initial.py | 2 +- .../0002_msg_db_hide_from_objects.py | 2 +- .../migrations/0003_auto_20140917_0756.py | 2 +- .../migrations/0004_auto_20150118_1631.py | 2 +- .../migrations/0005_auto_20150223_1517.py | 2 +- .../0006_channeldb_db_object_subscriptions.py | 2 +- evennia/comms/migrations/0007_msg_db_tags.py | 2 +- .../migrations/0008_auto_20160905_0902.py | 2 +- .../migrations/0009_auto_20160921_1731.py | 2 +- .../migrations/0010_auto_20161206_1912.py | 2 +- .../migrations/0011_auto_20170217_2039.py | 2 +- .../migrations/0011_auto_20170606_1731.py | 2 +- .../migrations/0012_merge_20170617_2017.py | 2 +- .../migrations/0013_auto_20170705_1726.py | 2 +- .../migrations/0014_auto_20170705_1736.py | 2 +- .../migrations/0015_auto_20170706_2041.py | 2 +- evennia/comms/models.py | 4 +- evennia/contrib/barter.py | 2 +- evennia/contrib/clothing.py | 4 +- evennia/contrib/custom_gametime.py | 4 +- evennia/contrib/egi_client/client.py | 4 +- evennia/contrib/extended_room.py | 4 +- .../contrib/ingame_python/callbackhandler.py | 2 +- evennia/contrib/ingame_python/commands.py | 10 +- evennia/contrib/ingame_python/scripts.py | 10 +- evennia/contrib/ingame_python/tests.py | 6 +- evennia/contrib/ingame_python/utils.py | 6 +- evennia/contrib/mail.py | 2 +- evennia/contrib/mapbuilder.py | 12 +- evennia/contrib/rplanguage.py | 4 +- evennia/contrib/rpsystem.py | 14 +- evennia/contrib/tests.py | 26 +- evennia/contrib/tutorial_world/__init__.py | 2 +- evennia/contrib/tutorial_world/objects.py | 2 +- evennia/contrib/tutorial_world/rooms.py | 2 +- evennia/contrib/wilderness.py | 6 +- evennia/game_template/server/conf/settings.py | 2 +- evennia/help/migrations/0001_initial.py | 2 +- .../migrations/0002_auto_20170606_1731.py | 2 +- evennia/help/models.py | 2 +- evennia/locks/lockfuncs.py | 2 +- evennia/locks/lockhandler.py | 6 +- evennia/locks/tests.py | 36 +- evennia/objects/manager.py | 12 +- evennia/objects/migrations/0001_initial.py | 2 +- .../migrations/0002_auto_20140917_0756.py | 2 +- ...r_defaultexit_defaultobject_defaultroom.py | 2 +- .../migrations/0004_auto_20150118_1622.py | 2 +- .../migrations/0005_auto_20150403_2339.py | 2 +- .../migrations/0006_auto_20170606_1731.py | 2 +- .../migrations/0007_objectdb_db_account.py | 2 +- .../migrations/0008_auto_20170705_1736.py | 2 +- .../0009_remove_objectdb_db_player.py | 2 +- evennia/objects/models.py | 2 +- evennia/objects/objects.py | 8 +- evennia/scripts/manager.py | 4 +- evennia/scripts/migrations/0001_initial.py | 2 +- .../migrations/0002_auto_20150118_1625.py | 2 +- ...techannelhandler_validateidmappercache_.py | 2 +- .../migrations/0004_auto_20150306_1354.py | 12 +- .../migrations/0005_auto_20150306_1441.py | 2 +- .../migrations/0006_auto_20150310_2249.py | 12 +- .../migrations/0007_auto_20150403_2339.py | 2 +- .../migrations/0008_auto_20170606_1731.py | 2 +- .../migrations/0009_scriptdb_db_account.py | 2 +- .../migrations/0010_auto_20170705_1736.py | 2 +- .../0011_remove_scriptdb_db_player.py | 2 +- evennia/scripts/models.py | 2 +- evennia/scripts/monitorhandler.py | 8 +- evennia/scripts/taskhandler.py | 12 +- evennia/scripts/tickerhandler.py | 30 +- evennia/server/amp.py | 6 +- evennia/server/evennia_launcher.py | 20 +- evennia/server/evennia_runner.py | 16 +- evennia/server/initial_setup.py | 2 +- evennia/server/inputfuncs.py | 8 +- evennia/server/migrations/0001_initial.py | 2 +- evennia/server/models.py | 2 +- evennia/server/portal/mssp.py | 2 +- evennia/server/portal/portal.py | 2 +- evennia/server/portal/portalsessionhandler.py | 14 +- evennia/server/portal/ssh.py | 2 +- evennia/server/portal/ssl.py | 2 +- evennia/server/portal/telnet_oob.py | 10 +- evennia/server/portal/webclient_ajax.py | 2 +- evennia/server/profiling/dummyrunner.py | 4 +- evennia/server/profiling/memplot.py | 2 +- evennia/server/profiling/test_queries.py | 2 +- evennia/server/profiling/timetrace.py | 2 +- evennia/server/server.py | 8 +- evennia/server/serversession.py | 4 +- evennia/server/session.py | 4 +- evennia/server/sessionhandler.py | 34 +- evennia/server/webserver.py | 6 +- evennia/settings_default.py | 4 +- evennia/typeclasses/attributes.py | 16 +- evennia/typeclasses/managers.py | 6 +- .../typeclasses/migrations/0001_initial.py | 2 +- .../migrations/0002_auto_20150109_0913.py | 2 +- ...ltplayer_defaultroom_defaultscript_dono.py | 2 +- .../migrations/0004_auto_20151101_1759.py | 2 +- .../migrations/0005_auto_20160625_1812.py | 2 +- ...o_add_dbmodel_value_for_tags_attributes.py | 2 +- .../0007_tag_migrations_may_be_slow.py | 2 +- .../migrations/0008_lock_and_perm_rename.py | 2 +- .../0009_rename_player_cmdsets_typeclasses.py | 2 +- .../0010_delete_old_player_tables.py | 2 +- evennia/typeclasses/models.py | 4 +- evennia/typeclasses/tags.py | 8 +- evennia/utils/__init__.py | 2 +- evennia/utils/ansi.py | 18 +- evennia/utils/batchprocessors.py | 2 +- evennia/utils/create.py | 8 +- evennia/utils/dbserialize.py | 30 +- evennia/utils/eveditor.py | 6 +- evennia/utils/evform.py | 24 +- evennia/utils/evmenu.py | 10 +- evennia/utils/evtable.py | 26 +- evennia/utils/gametime.py | 2 +- evennia/utils/idmapper/models.py | 6 +- evennia/utils/idmapper/tests.py | 10 +- evennia/utils/inlinefuncs.py | 928 +++++++++--------- evennia/utils/logger.py | 2 +- evennia/utils/picklefield.py | 2 +- evennia/utils/spawner.py | 10 +- evennia/utils/tests/test_evform.py | 78 +- evennia/utils/tests/test_evmenu.py | 2 +- evennia/utils/tests/test_tagparsing.py | 52 +- evennia/utils/text2html.py | 4 +- evennia/utils/txws.py | 4 +- evennia/utils/utils.py | 34 +- evennia/web/webclient/views.py | 2 +- 157 files changed, 976 insertions(+), 976 deletions(-) diff --git a/bin/project_rename.py b/bin/project_rename.py index 8f22d75f1c..2d5bcb779e 100644 --- a/bin/project_rename.py +++ b/bin/project_rename.py @@ -6,7 +6,7 @@ Created for the Player->Account renaming Griatch 2017, released under the BSD license. """ -from __future__ import print_function + import re import sys @@ -130,7 +130,7 @@ def rename_in_tree(path, in_list, out_list, excl_list, fileend_list, is_interact replacements in each file. """ - repl_mapping = zip(in_list, out_list) + repl_mapping = list(zip(in_list, out_list)) for root, dirs, files in os.walk(path): @@ -155,13 +155,13 @@ def rename_in_tree(path, in_list, out_list, excl_list, fileend_list, is_interact for src, dst in repl_mapping: new_file = _case_sensitive_replace(new_file, src, dst) if new_file != file: - inp = raw_input(_green("Rename %s\n -> %s\n Y/[N]? > " % (file, new_file))) + inp = input(_green("Rename %s\n -> %s\n Y/[N]? > " % (file, new_file))) if inp.upper() == 'Y': new_full_path = os.path.join(root, new_file) try: os.rename(full_path, new_full_path) except OSError as err: - raw_input(_red("Could not rename - %s (return to skip)" % err)) + input(_red("Could not rename - %s (return to skip)" % err)) else: print("... Renamed.") else: @@ -171,12 +171,12 @@ def rename_in_tree(path, in_list, out_list, excl_list, fileend_list, is_interact for src, dst in repl_mapping: new_root = _case_sensitive_replace(new_root, src, dst) if new_root != root: - inp = raw_input(_green("Dir Rename %s\n -> %s\n Y/[N]? > " % (root, new_root))) + inp = input(_green("Dir Rename %s\n -> %s\n Y/[N]? > " % (root, new_root))) if inp.upper() == 'Y': try: os.rename(root, new_root) except OSError as err: - raw_input(_red("Could not rename - %s (return to skip)" % err)) + input(_red("Could not rename - %s (return to skip)" % err)) else: print("... Renamed.") else: @@ -204,7 +204,7 @@ def rename_in_file(path, in_list, out_list, is_interactive): with open(path, 'r') as fil: org_text = fil.read() - repl_mapping = zip(in_list, out_list) + repl_mapping = list(zip(in_list, out_list)) if not is_interactive: # just replace everything immediately @@ -239,12 +239,12 @@ def rename_in_file(path, in_list, out_list, is_interactive): while True: - for iline, renamed_line in sorted(renamed.items(), key=lambda tup: tup[0]): + for iline, renamed_line in sorted(list(renamed.items()), key=lambda tup: tup[0]): print("%3i orig: %s" % (iline + 1, org_lines[iline])) print(" new : %s" % (_yellow(renamed_line))) print(_green("%s (%i lines changed)" % (path, len(renamed)))) - ret = raw_input(_green("Choose: " + ret = input(_green("Choose: " "[q]uit, " "[h]elp, " "[s]kip file, " @@ -261,7 +261,7 @@ def rename_in_file(path, in_list, out_list, is_interactive): break elif ret == "a": # save result - for iline, renamed_line in renamed.items(): + for iline, renamed_line in list(renamed.items()): org_lines[iline] = renamed_line if FAKE_MODE: @@ -275,12 +275,12 @@ def rename_in_file(path, in_list, out_list, is_interactive): print("Quit renaming program.") sys.exit() elif ret == "h": - raw_input(_HELP_TEXT.format(sources=in_list, targets=out_list)) + input(_HELP_TEXT.format(sources=in_list, targets=out_list)) elif ret.startswith("i"): # ignore one or more lines ignores = [int(ind) - 1 for ind in ret[1:].split(',') if ind.strip().isdigit()] if not ignores: - raw_input("Ignore example: i 2,7,34,133\n (return to continue)") + input("Ignore example: i 2,7,34,133\n (return to continue)") continue for ign in ignores: renamed.pop(ign, None) diff --git a/evennia/__init__.py b/evennia/__init__.py index 6fdc4aaece..92026cb1ec 100644 --- a/evennia/__init__.py +++ b/evennia/__init__.py @@ -17,8 +17,8 @@ to launch such a shell (using python or ipython depending on your install). See www.evennia.com for full documentation. """ -from __future__ import print_function -from __future__ import absolute_import + + from builtins import object # Delayed loading of properties diff --git a/evennia/accounts/accounts.py b/evennia/accounts/accounts.py index 9feed46d2e..aa68524383 100644 --- a/evennia/accounts/accounts.py +++ b/evennia/accounts/accounts.py @@ -491,7 +491,7 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)): """ # handle me, self and *me, *self - if isinstance(searchdata, basestring): + if isinstance(searchdata, str): # handle wrapping of common terms if searchdata.lower() in ("me", "*me", "self", "*self",): return self @@ -983,7 +983,7 @@ class DefaultGuest(DefaultAccount): characters = self.db._playable_characters for character in characters: if character: - print "deleting Character:", character + print("deleting Character:", character) character.delete() def at_post_disconnect(self, **kwargs): diff --git a/evennia/accounts/bots.py b/evennia/accounts/bots.py index b0a765fbba..f772e7c4d6 100644 --- a/evennia/accounts/bots.py +++ b/evennia/accounts/bots.py @@ -3,7 +3,7 @@ Bots are a special child typeclasses of Account that are controlled by the server. """ -from __future__ import print_function + import time from django.conf import settings from evennia.accounts.accounts import DefaultAccount diff --git a/evennia/accounts/manager.py b/evennia/accounts/manager.py index c612cf930d..ade900ea70 100644 --- a/evennia/accounts/manager.py +++ b/evennia/accounts/manager.py @@ -165,9 +165,9 @@ class AccountDBManager(TypedObjectManager, UserManager): if typeclass: # we accept both strings and actual typeclasses if callable(typeclass): - typeclass = u"%s.%s" % (typeclass.__module__, typeclass.__name__) + typeclass = "%s.%s" % (typeclass.__module__, typeclass.__name__) else: - typeclass = u"%s" % typeclass + typeclass = "%s" % typeclass query["db_typeclass_path"] = typeclass if exact: return self.filter(**query) diff --git a/evennia/accounts/migrations/0001_initial.py b/evennia/accounts/migrations/0001_initial.py index d8d267f20c..787eaa79cb 100644 --- a/evennia/accounts/migrations/0001_initial.py +++ b/evennia/accounts/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations import django.utils.timezone diff --git a/evennia/accounts/migrations/0002_move_defaults.py b/evennia/accounts/migrations/0002_move_defaults.py index 461525a4cc..f0b3d6dd51 100644 --- a/evennia/accounts/migrations/0002_move_defaults.py +++ b/evennia/accounts/migrations/0002_move_defaults.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/evennia/accounts/migrations/0003_auto_20150209_2234.py b/evennia/accounts/migrations/0003_auto_20150209_2234.py index ccdb2fb897..81bb39abdb 100644 --- a/evennia/accounts/migrations/0003_auto_20150209_2234.py +++ b/evennia/accounts/migrations/0003_auto_20150209_2234.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/evennia/accounts/migrations/0004_auto_20150403_2339.py b/evennia/accounts/migrations/0004_auto_20150403_2339.py index 36d8110122..b1412ae9a8 100644 --- a/evennia/accounts/migrations/0004_auto_20150403_2339.py +++ b/evennia/accounts/migrations/0004_auto_20150403_2339.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations import evennia.accounts.manager diff --git a/evennia/accounts/migrations/0005_auto_20160905_0902.py b/evennia/accounts/migrations/0005_auto_20160905_0902.py index 7ceaad2ac0..22f5955c1b 100644 --- a/evennia/accounts/migrations/0005_auto_20160905_0902.py +++ b/evennia/accounts/migrations/0005_auto_20160905_0902.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.9 on 2016-09-05 09:02 -from __future__ import unicode_literals + import django.core.validators from django.db import migrations, models diff --git a/evennia/accounts/migrations/0006_auto_20170606_1731.py b/evennia/accounts/migrations/0006_auto_20170606_1731.py index e48114b23c..c149d8b113 100644 --- a/evennia/accounts/migrations/0006_auto_20170606_1731.py +++ b/evennia/accounts/migrations/0006_auto_20170606_1731.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.2 on 2017-06-06 17:31 -from __future__ import unicode_literals + import django.contrib.auth.validators from django.db import migrations, models diff --git a/evennia/accounts/migrations/0007_copy_player_to_account.py b/evennia/accounts/migrations/0007_copy_player_to_account.py index ea1e00448c..0e573f2fa5 100644 --- a/evennia/accounts/migrations/0007_copy_player_to_account.py +++ b/evennia/accounts/migrations/0007_copy_player_to_account.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.2 on 2017-07-03 19:17 -from __future__ import unicode_literals + from django.apps import apps as global_apps from django.db import migrations diff --git a/evennia/accounts/models.py b/evennia/accounts/models.py index bf391b2d82..d9f59a43b0 100644 --- a/evennia/accounts/models.py +++ b/evennia/accounts/models.py @@ -139,7 +139,7 @@ class AccountDB(TypedObject, AbstractUser): return smart_str("%s(account %s)" % (self.name, self.dbid)) def __unicode__(self): - return u"%s(account#%s)" % (self.name, self.dbid) + return "%s(account#%s)" % (self.name, self.dbid) #@property def __username_get(self): diff --git a/evennia/commands/cmdhandler.py b/evennia/commands/cmdhandler.py index 44304b4ea1..b934932352 100644 --- a/evennia/commands/cmdhandler.py +++ b/evennia/commands/cmdhandler.py @@ -190,7 +190,7 @@ def _progressive_cmd_run(cmd, generator, response=None): try: if response is None: - value = generator.next() + value = next(generator) else: value = generator.send(response) except StopIteration: @@ -198,7 +198,7 @@ def _progressive_cmd_run(cmd, generator, response=None): else: if isinstance(value, (int, float)): utils.delay(value, _progressive_cmd_run, cmd, generator) - elif isinstance(value, basestring): + elif isinstance(value, str): _GET_INPUT(cmd.caller, value, _process_input, cmd=cmd, generator=generator) else: raise ValueError("unknown type for a yielded value in command: {}".format(type(value))) @@ -443,7 +443,7 @@ def get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string) tempmergers[prio] = cmdset # sort cmdsets after reverse priority (highest prio are merged in last) - cmdsets = yield sorted(tempmergers.values(), key=lambda x: x.priority) + cmdsets = yield sorted(list(tempmergers.values()), key=lambda x: x.priority) # Merge all command sets into one, beginning with the lowest-prio one cmdset = cmdsets[0] @@ -567,7 +567,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess returnValue(cmd) # assign custom kwargs to found cmd object - for key, val in kwargs.items(): + for key, val in list(kwargs.items()): setattr(cmd, key, val) _COMMAND_NESTING[called_by] += 1 diff --git a/evennia/commands/cmdparser.py b/evennia/commands/cmdparser.py index e104233e49..c9055db8ca 100644 --- a/evennia/commands/cmdparser.py +++ b/evennia/commands/cmdparser.py @@ -5,7 +5,7 @@ replacing cmdparser function. The replacement parser must accept the same inputs as the default one. """ -from __future__ import division + import re from django.conf import settings @@ -70,7 +70,7 @@ def cmdparser(raw_string, cmdset, caller, match_index=None): the `raw_cmdname` is the cmdname unmodified by eventual prefix-stripping. """ - cmdlen, strlen = len(unicode(cmdname)), len(unicode(string)) + cmdlen, strlen = len(str(cmdname)), len(str(string)) mratio = 1 - (strlen - cmdlen) / (1.0 * strlen) args = string[cmdlen:] return (cmdname, args, cmdobj, cmdlen, mratio, raw_cmdname) diff --git a/evennia/commands/cmdsethandler.py b/evennia/commands/cmdsethandler.py index 84eea1fdc5..20b869dc39 100644 --- a/evennia/commands/cmdsethandler.py +++ b/evennia/commands/cmdsethandler.py @@ -422,13 +422,13 @@ class CmdSetHandler(object): it's a 'quirk' that has to be documented. """ - if not (isinstance(cmdset, basestring) or utils.inherits_from(cmdset, CmdSet)): + if not (isinstance(cmdset, str) or utils.inherits_from(cmdset, CmdSet)): string = _("Only CmdSets can be added to the cmdsethandler!") raise Exception(string) if callable(cmdset): cmdset = cmdset(self.obj) - elif isinstance(cmdset, basestring): + elif isinstance(cmdset, str): # this is (maybe) a python path. Try to import from cache. cmdset = self._import_cmdset(cmdset) if cmdset and cmdset.key != '_CMDSET_ERROR': @@ -586,11 +586,11 @@ class CmdSetHandler(object): """ if callable(cmdset) and hasattr(cmdset, 'path'): # try it as a callable - print "Try callable", cmdset + print("Try callable", cmdset) if must_be_default: return self.cmdset_stack and (self.cmdset_stack[0].path == cmdset.path) else: - print [cset.path for cset in self.cmdset_stack], cmdset.path + print([cset.path for cset in self.cmdset_stack], cmdset.path) return any([cset for cset in self.cmdset_stack if cset.path == cmdset.path]) else: diff --git a/evennia/commands/command.py b/evennia/commands/command.py index 48a4b132da..c30c74222e 100644 --- a/evennia/commands/command.py +++ b/evennia/commands/command.py @@ -65,7 +65,7 @@ def _init_command(cls, **kwargs): temp.append(lockstring) cls.lock_storage = ";".join(temp) - if hasattr(cls, 'arg_regex') and isinstance(cls.arg_regex, basestring): + if hasattr(cls, 'arg_regex') and isinstance(cls.arg_regex, str): cls.arg_regex = re.compile(r"%s" % cls.arg_regex, re.I + re.UNICODE) if not hasattr(cls, "auto_help"): cls.auto_help = True @@ -266,7 +266,7 @@ class Command(with_metaclass(CommandMeta, object)): caches are properly updated as well. """ - if isinstance(new_aliases, basestring): + if isinstance(new_aliases, str): new_aliases = new_aliases.split(';') aliases = (str(alias).strip().lower() for alias in make_iter(new_aliases)) self.aliases = list(set(alias for alias in aliases if alias != self.key)) diff --git a/evennia/commands/default/account.py b/evennia/commands/default/account.py index 90c2fdb954..ecde6d8b32 100644 --- a/evennia/commands/default/account.py +++ b/evennia/commands/default/account.py @@ -502,13 +502,13 @@ class CmdOption(COMMAND_DEFAULT_CLASS): options["SCREENWIDTH"] = options["SCREENWIDTH"][0] else: options["SCREENWIDTH"] = " \n".join("%s : %s" % (screenid, size) - for screenid, size in options["SCREENWIDTH"].iteritems()) + for screenid, size in options["SCREENWIDTH"].items()) if "SCREENHEIGHT" in options: if len(options["SCREENHEIGHT"]) == 1: options["SCREENHEIGHT"] = options["SCREENHEIGHT"][0] else: options["SCREENHEIGHT"] = " \n".join("%s : %s" % (screenid, size) - for screenid, size in options["SCREENHEIGHT"].iteritems()) + for screenid, size in options["SCREENHEIGHT"].items()) options.pop("TTYPE", None) header = ("Name", "Value", "Saved") if saved_options else ("Name", "Value") diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index ff487626a1..5b19f2006b 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -1541,7 +1541,7 @@ class CmdSetAttribute(ObjManipCommand): def load(caller): """Called for the editor to load the buffer""" old_value = obj.attributes.get(attr) - if old_value is not None and not isinstance(old_value, basestring): + if old_value is not None and not isinstance(old_value, str): typ = type(old_value).__name__ self.caller.msg("|RWARNING! Saving this buffer will overwrite the " "current attribute (of type %s) with a string!|n" % typ) @@ -1937,7 +1937,7 @@ class CmdExamine(ObjManipCommand): Formats a single attribute line. """ if crop: - if not isinstance(value, basestring): + if not isinstance(value, str): value = utils.to_str(value, force_string=True) value = utils.crop(value) value = utils.to_unicode(value) @@ -2058,7 +2058,7 @@ class CmdExamine(ObjManipCommand): except (TypeError, AttributeError): # an error means we are merging an object without a session pass - all_cmdsets = [cmdset for cmdset in dict(all_cmdsets).values()] + all_cmdsets = [cmdset for cmdset in list(dict(all_cmdsets).values())] all_cmdsets.sort(key=lambda x: x.priority, reverse=True) string += "\n|wMerged Cmdset(s)|n:\n %s" % ("\n ".join("%s [%s] (%s, prio %s)" % ( cmdset.path, cmdset.key, cmdset.mergetype, cmdset.priority) for cmdset in all_cmdsets)) @@ -2707,7 +2707,7 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS): self.caller.msg(string) return - if isinstance(prototype, basestring): + if isinstance(prototype, str): # A prototype key keystr = prototype prototype = prototypes.get(prototype, None) diff --git a/evennia/commands/default/comms.py b/evennia/commands/default/comms.py index 53e047a2ac..8ab75f8727 100644 --- a/evennia/commands/default/comms.py +++ b/evennia/commands/default/comms.py @@ -754,7 +754,7 @@ class CmdPage(COMMAND_DEFAULT_CLASS): recobjs = [] for receiver in set(receivers): - if isinstance(receiver, basestring): + if isinstance(receiver, str): pobj = caller.search(receiver) elif hasattr(receiver, 'character'): pobj = receiver diff --git a/evennia/commands/default/system.py b/evennia/commands/default/system.py index fe5efa337d..99879c3b10 100644 --- a/evennia/commands/default/system.py +++ b/evennia/commands/default/system.py @@ -3,7 +3,7 @@ System commands """ -from __future__ import division + import traceback import os @@ -440,7 +440,7 @@ class CmdObjects(COMMAND_DEFAULT_CLASS): typetable = EvTable("|wtypeclass|n", "|wcount|n", "|w%%|n", border="table", align="l") typetable.align = 'l' dbtotals = ObjectDB.objects.object_totals() - for path, count in dbtotals.items(): + for path, count in list(dbtotals.items()): typetable.add_row(path, count, "%.2f" % ((float(count) / nobjs) * 100)) # last N table @@ -487,7 +487,7 @@ class CmdAccounts(COMMAND_DEFAULT_CLASS): # typeclass table dbtotals = AccountDB.objects.object_totals() typetable = EvTable("|wtypeclass|n", "|wcount|n", "|w%%|n", border="cells", align="l") - for path, count in dbtotals.items(): + for path, count in list(dbtotals.items()): typetable.add_row(path, count, "%.2f" % ((float(count) / naccounts) * 100)) # last N table plyrs = AccountDB.objects.all().order_by("db_date_created")[max(0, naccounts - nlim):] @@ -544,7 +544,7 @@ class CmdService(COMMAND_DEFAULT_CLASS): table = EvTable("|wService|n (use @services/start|stop|delete)", "|wstatus", align="l") for service in service_collection.services: table.add_row(service.name, service.running and "|gRunning" or "|rNot Running") - caller.msg(unicode(table)) + caller.msg(str(table)) return # Get the service to start / stop @@ -663,7 +663,7 @@ class CmdTime(COMMAND_DEFAULT_CLASS): table2.add_row("Total time passed:", utils.time_format(gametime.gametime(), 2)) table2.add_row("Current time ", datetime.datetime.fromtimestamp(gametime.gametime(absolute=True))) table2.reformat_column(0, width=30) - self.caller.msg(unicode(table1) + "\n" + unicode(table2)) + self.caller.msg(str(table1) + "\n" + str(table2)) class CmdServerLoad(COMMAND_DEFAULT_CLASS): @@ -799,7 +799,7 @@ class CmdServerLoad(COMMAND_DEFAULT_CLASS): # object cache count (note that sys.getsiseof is not called so this works for pypy too. total_num, cachedict = _IDMAPPER.cache_size() - sorted_cache = sorted([(key, num) for key, num in cachedict.items() if num > 0], + sorted_cache = sorted([(key, num) for key, num in list(cachedict.items()) if num > 0], key=lambda tup: tup[1], reverse=True) memtable = EvTable("entity name", "number", "idmapper %", align="l") for tup in sorted_cache: @@ -841,4 +841,4 @@ class CmdTickers(COMMAND_DEFAULT_CLASS): sub[1] if sub[1] else sub[2], sub[4] or "[Unset]", "*" if sub[5] else "-") - self.caller.msg("|wActive tickers|n:\n" + unicode(table)) + self.caller.msg("|wActive tickers|n:\n" + str(table)) diff --git a/evennia/commands/default/tests.py b/evennia/commands/default/tests.py index ffb63ef723..7d79e6ad5c 100644 --- a/evennia/commands/default/tests.py +++ b/evennia/commands/default/tests.py @@ -78,7 +78,7 @@ class CommandTest(EvenniaTest): cmdobj.parse() ret = cmdobj.func() if isinstance(ret, types.GeneratorType): - ret.next() + next(ret) cmdobj.at_post_cmd() except StopIteration: pass @@ -128,9 +128,9 @@ class TestGeneral(CommandTest): self.call(general.CmdNick(), "testalias = testaliasedstring1", "Nick 'testalias' mapped to 'testaliasedstring1'.") self.call(general.CmdNick(), "/account testalias = testaliasedstring2", "Nick 'testalias' mapped to 'testaliasedstring2'.") self.call(general.CmdNick(), "/object testalias = testaliasedstring3", "Nick 'testalias' mapped to 'testaliasedstring3'.") - self.assertEqual(u"testaliasedstring1", self.char1.nicks.get("testalias")) - self.assertEqual(u"testaliasedstring2", self.char1.nicks.get("testalias", category="account")) - self.assertEqual(u"testaliasedstring3", self.char1.nicks.get("testalias", category="object")) + self.assertEqual("testaliasedstring1", self.char1.nicks.get("testalias")) + self.assertEqual("testaliasedstring2", self.char1.nicks.get("testalias", category="account")) + self.assertEqual("testaliasedstring3", self.char1.nicks.get("testalias", category="object")) def test_get_and_drop(self): self.call(general.CmdGet(), "Obj", "You pick up Obj.") diff --git a/evennia/comms/channelhandler.py b/evennia/comms/channelhandler.py index 02c9e19291..3ed4cc0212 100644 --- a/evennia/comms/channelhandler.py +++ b/evennia/comms/channelhandler.py @@ -271,7 +271,7 @@ class ChannelHandler(object): if channelname: channel = self._cached_channels.get(channelname.lower(), None) return [channel] if channel else [] - return self._cached_channels.values() + return list(self._cached_channels.values()) def get_cmdset(self, source_object): """ @@ -292,7 +292,7 @@ class ChannelHandler(object): else: # create a new cmdset holding all viable channels chan_cmdset = None - chan_cmds = [channelcmd for channel, channelcmd in self._cached_channel_cmds.iteritems() + chan_cmds = [channelcmd for channel, channelcmd in self._cached_channel_cmds.items() if channel.subscriptions.has(source_object) and channelcmd.access(source_object, 'send')] if chan_cmds: diff --git a/evennia/comms/comms.py b/evennia/comms/comms.py index e40de664d1..56cd054720 100644 --- a/evennia/comms/comms.py +++ b/evennia/comms/comms.py @@ -325,7 +325,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): """ senders = make_iter(senders) if senders else [] - if isinstance(msgobj, basestring): + if isinstance(msgobj, str): # given msgobj is a string - convert to msgobject (always TempMsg) msgobj = TempMsg(senders=senders, header=header, message=msgobj, channels=[self]) # we store the logging setting for use in distribute_message() diff --git a/evennia/comms/managers.py b/evennia/comms/managers.py index f50d813123..af0a3ec1d5 100644 --- a/evennia/comms/managers.py +++ b/evennia/comms/managers.py @@ -3,7 +3,7 @@ These managers define helper methods for accessing the database from Comm system components. """ -from __future__ import print_function + from django.db.models import Q from evennia.typeclasses.managers import (TypedObjectManager, TypeclassManager) @@ -43,9 +43,9 @@ def dbref(inp, reqhash=True): dbref, otherwise `None`. """ - if reqhash and not (isinstance(inp, basestring) and inp.startswith("#")): + if reqhash and not (isinstance(inp, str) and inp.startswith("#")): return None - if isinstance(inp, basestring): + if isinstance(inp, str): inp = inp.lstrip('#') try: if int(inp) < 0: @@ -77,7 +77,7 @@ def identify_object(inp): return inp, "object" elif clsname == "ChannelDB": return inp, "channel" - if isinstance(inp, basestring): + if isinstance(inp, str): return inp, "string" elif dbref(inp): return dbref(inp), "dbref" diff --git a/evennia/comms/migrations/0001_initial.py b/evennia/comms/migrations/0001_initial.py index 611e4061c0..4e502e3145 100644 --- a/evennia/comms/migrations/0001_initial.py +++ b/evennia/comms/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/evennia/comms/migrations/0002_msg_db_hide_from_objects.py b/evennia/comms/migrations/0002_msg_db_hide_from_objects.py index 58224b1bde..20e6c1a126 100644 --- a/evennia/comms/migrations/0002_msg_db_hide_from_objects.py +++ b/evennia/comms/migrations/0002_msg_db_hide_from_objects.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/evennia/comms/migrations/0003_auto_20140917_0756.py b/evennia/comms/migrations/0003_auto_20140917_0756.py index 1ee5f874da..5f7be96f8f 100644 --- a/evennia/comms/migrations/0003_auto_20140917_0756.py +++ b/evennia/comms/migrations/0003_auto_20140917_0756.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations from django.conf import settings diff --git a/evennia/comms/migrations/0004_auto_20150118_1631.py b/evennia/comms/migrations/0004_auto_20150118_1631.py index 2d4ae99159..2e602f9fad 100644 --- a/evennia/comms/migrations/0004_auto_20150118_1631.py +++ b/evennia/comms/migrations/0004_auto_20150118_1631.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/evennia/comms/migrations/0005_auto_20150223_1517.py b/evennia/comms/migrations/0005_auto_20150223_1517.py index a26a6f63e7..b8289f5dea 100644 --- a/evennia/comms/migrations/0005_auto_20150223_1517.py +++ b/evennia/comms/migrations/0005_auto_20150223_1517.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import migrations diff --git a/evennia/comms/migrations/0006_channeldb_db_object_subscriptions.py b/evennia/comms/migrations/0006_channeldb_db_object_subscriptions.py index 570d24b136..568da97c7c 100644 --- a/evennia/comms/migrations/0006_channeldb_db_object_subscriptions.py +++ b/evennia/comms/migrations/0006_channeldb_db_object_subscriptions.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/evennia/comms/migrations/0007_msg_db_tags.py b/evennia/comms/migrations/0007_msg_db_tags.py index 899d6f946a..e54a058a2b 100644 --- a/evennia/comms/migrations/0007_msg_db_tags.py +++ b/evennia/comms/migrations/0007_msg_db_tags.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import migrations, models diff --git a/evennia/comms/migrations/0008_auto_20160905_0902.py b/evennia/comms/migrations/0008_auto_20160905_0902.py index cad7637cd3..4a3f2071a7 100644 --- a/evennia/comms/migrations/0008_auto_20160905_0902.py +++ b/evennia/comms/migrations/0008_auto_20160905_0902.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.9 on 2016-09-05 09:02 -from __future__ import unicode_literals + from django.db import migrations diff --git a/evennia/comms/migrations/0009_auto_20160921_1731.py b/evennia/comms/migrations/0009_auto_20160921_1731.py index 3be041b88a..f803704d3f 100644 --- a/evennia/comms/migrations/0009_auto_20160921_1731.py +++ b/evennia/comms/migrations/0009_auto_20160921_1731.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.9 on 2016-09-21 17:31 -from __future__ import unicode_literals + from django.conf import settings from django.db import migrations, models diff --git a/evennia/comms/migrations/0010_auto_20161206_1912.py b/evennia/comms/migrations/0010_auto_20161206_1912.py index 4da49b8447..b3a10ebe98 100644 --- a/evennia/comms/migrations/0010_auto_20161206_1912.py +++ b/evennia/comms/migrations/0010_auto_20161206_1912.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.11 on 2016-12-06 19:12 -from __future__ import unicode_literals + from django.conf import settings from django.db import migrations, models diff --git a/evennia/comms/migrations/0011_auto_20170217_2039.py b/evennia/comms/migrations/0011_auto_20170217_2039.py index b13e6dcec0..13578b031a 100644 --- a/evennia/comms/migrations/0011_auto_20170217_2039.py +++ b/evennia/comms/migrations/0011_auto_20170217_2039.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.11 on 2017-02-17 20:39 -from __future__ import unicode_literals + from django.db import migrations, models diff --git a/evennia/comms/migrations/0011_auto_20170606_1731.py b/evennia/comms/migrations/0011_auto_20170606_1731.py index f4df669e2d..128e22bd01 100644 --- a/evennia/comms/migrations/0011_auto_20170606_1731.py +++ b/evennia/comms/migrations/0011_auto_20170606_1731.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.2 on 2017-06-06 17:31 -from __future__ import unicode_literals + from django.conf import settings from django.db import migrations, models diff --git a/evennia/comms/migrations/0012_merge_20170617_2017.py b/evennia/comms/migrations/0012_merge_20170617_2017.py index a91de6d29c..2c06828618 100644 --- a/evennia/comms/migrations/0012_merge_20170617_2017.py +++ b/evennia/comms/migrations/0012_merge_20170617_2017.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.2 on 2017-06-17 20:17 -from __future__ import unicode_literals + from django.db import migrations diff --git a/evennia/comms/migrations/0013_auto_20170705_1726.py b/evennia/comms/migrations/0013_auto_20170705_1726.py index db42debd2e..b8a863fcb9 100644 --- a/evennia/comms/migrations/0013_auto_20170705_1726.py +++ b/evennia/comms/migrations/0013_auto_20170705_1726.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.2 on 2017-07-05 17:26 -from __future__ import unicode_literals + from django.db import migrations, models, connection diff --git a/evennia/comms/migrations/0014_auto_20170705_1736.py b/evennia/comms/migrations/0014_auto_20170705_1736.py index 474f4b13d6..224aa2dda4 100644 --- a/evennia/comms/migrations/0014_auto_20170705_1736.py +++ b/evennia/comms/migrations/0014_auto_20170705_1736.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.2 on 2017-07-05 17:36 -from __future__ import unicode_literals + from django.db import migrations diff --git a/evennia/comms/migrations/0015_auto_20170706_2041.py b/evennia/comms/migrations/0015_auto_20170706_2041.py index 62b793646b..58d88eab25 100644 --- a/evennia/comms/migrations/0015_auto_20170706_2041.py +++ b/evennia/comms/migrations/0015_auto_20170706_2041.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.2 on 2017-07-06 20:41 -from __future__ import unicode_literals + from django.db import migrations, connection diff --git a/evennia/comms/models.py b/evennia/comms/models.py index b1a5a37ed9..81555b70ad 100644 --- a/evennia/comms/models.py +++ b/evennia/comms/models.py @@ -166,7 +166,7 @@ class Msg(SharedMemoryModel): for sender in make_iter(senders): if not sender: continue - if isinstance(sender, basestring): + if isinstance(sender, str): self.db_sender_external = sender self.extra_senders.append(sender) self.save(update_fields=["db_sender_external"]) @@ -203,7 +203,7 @@ class Msg(SharedMemoryModel): for sender in make_iter(senders): if not sender: continue - if isinstance(sender, basestring): + if isinstance(sender, str): self.db_sender_external = "" self.save(update_fields=["db_sender_external"]) if not hasattr(sender, "__dbclass__"): diff --git a/evennia/contrib/barter.py b/evennia/contrib/barter.py index 71ea33694b..3171d4e297 100644 --- a/evennia/contrib/barter.py +++ b/evennia/contrib/barter.py @@ -93,7 +93,7 @@ cmdset. This will make the trade (or barter) command available in-game. """ -from __future__ import print_function + from builtins import object from evennia import Command, DefaultScript, CmdSet diff --git a/evennia/contrib/clothing.py b/evennia/contrib/clothing.py index 4e1935c99f..dd61c5f3db 100644 --- a/evennia/contrib/clothing.py +++ b/evennia/contrib/clothing.py @@ -187,7 +187,7 @@ def clothing_type_count(clothes_list): for garment in clothes_list: if garment.db.clothing_type: type = garment.db.clothing_type - if type not in types_count.keys(): + if type not in list(types_count.keys()): types_count[type] = 1 else: types_count[type] += 1 @@ -380,7 +380,7 @@ class CmdWear(MuxCommand): # Apply individual clothing type limits. if clothing.db.clothing_type and not clothing.db.worn: type_count = single_type_count(get_worn_clothes(self.caller), clothing.db.clothing_type) - if clothing.db.clothing_type in CLOTHING_TYPE_LIMIT.keys(): + if clothing.db.clothing_type in list(CLOTHING_TYPE_LIMIT.keys()): if type_count >= CLOTHING_TYPE_LIMIT[clothing.db.clothing_type]: self.caller.msg("You can't wear any more clothes of the type '%s'." % clothing.db.clothing_type) return diff --git a/evennia/contrib/custom_gametime.py b/evennia/contrib/custom_gametime.py index 42a53d1386..1446fcdf13 100644 --- a/evennia/contrib/custom_gametime.py +++ b/evennia/contrib/custom_gametime.py @@ -105,7 +105,7 @@ def gametime_to_realtime(format=False, **kwargs): """ # Dynamically creates the list of units based on kwarg names and UNITs list rtime = 0 - for name, value in kwargs.items(): + for name, value in list(kwargs.items()): # Allow plural names (like mins instead of min) if name not in UNITS and name.endswith("s"): name = name[:-1] @@ -197,7 +197,7 @@ def real_seconds_until(**kwargs): # For each keyword, add in the unit's units.append(1) higher_unit = None - for unit, value in kwargs.items(): + for unit, value in list(kwargs.items()): # Get the unit's index if unit not in UNITS: raise ValueError("unknown unit".format(unit)) diff --git a/evennia/contrib/egi_client/client.py b/evennia/contrib/egi_client/client.py index 6bb49db2e4..c3cb902801 100644 --- a/evennia/contrib/egi_client/client.py +++ b/evennia/contrib/egi_client/client.py @@ -1,4 +1,4 @@ -import urllib +import urllib.request, urllib.parse, urllib.error import platform import warnings @@ -107,7 +107,7 @@ class EvenniaGameIndexClient(object): 'django_version': django.get_version(), 'server_platform': platform.platform(), } - data = urllib.urlencode(values) + data = urllib.parse.urlencode(values) d = agent.request( 'POST', self.report_url, diff --git a/evennia/contrib/extended_room.py b/evennia/contrib/extended_room.py index 6823ede50e..7204fa752e 100644 --- a/evennia/contrib/extended_room.py +++ b/evennia/contrib/extended_room.py @@ -66,7 +66,7 @@ Installation/testing: 3) Use `desc` and `detail` to customize the room, then play around! """ -from __future__ import division + import datetime import re @@ -398,7 +398,7 @@ class CmdExtendedDesc(default_cmds.CmdDesc): # No args given. Return all details on location string = "|wDetails on %s|n:" % location details = "\n".join(" |w%s|n: %s" - % (key, utils.crop(text)) for key, text in location.db.details.items()) + % (key, utils.crop(text)) for key, text in list(location.db.details.items())) caller.msg("%s\n%s" % (string, details) if details else "%s None." % string) return if not self.rhs: diff --git a/evennia/contrib/ingame_python/callbackhandler.py b/evennia/contrib/ingame_python/callbackhandler.py index 625bfa182b..bb0cddc597 100644 --- a/evennia/contrib/ingame_python/callbackhandler.py +++ b/evennia/contrib/ingame_python/callbackhandler.py @@ -36,7 +36,7 @@ class CallbackHandler(object): handler = type(self).script if handler: dicts = handler.get_callbacks(self.obj) - for callback_name, in_list in dicts.items(): + for callback_name, in_list in list(dicts.items()): new_list = [] for callback in in_list: callback = self.format_callback(callback) diff --git a/evennia/contrib/ingame_python/commands.py b/evennia/contrib/ingame_python/commands.py index 138a705297..a8d14ba476 100644 --- a/evennia/contrib/ingame_python/commands.py +++ b/evennia/contrib/ingame_python/commands.py @@ -253,7 +253,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS): row.append("Yes" if callback.get("valid") else "No") table.add_row(*row) - self.msg(unicode(table)) + self.msg(str(table)) else: names = list(set(list(types.keys()) + list(callbacks.keys()))) table = EvTable("Callback name", "Number", "Description", @@ -269,7 +269,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS): description = description.strip("\n").splitlines()[0] table.add_row(name, no, description) - self.msg(unicode(table)) + self.msg(str(table)) def add_callback(self): """Add a callback.""" @@ -457,7 +457,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS): updated_on = "|gUnknown|n" table.add_row(obj.id, type_name, obj, name, by, updated_on) - self.msg(unicode(table)) + self.msg(str(table)) return # An object was specified @@ -503,7 +503,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS): obj = self.obj callback_name = self.callback_name handler = self.handler - tasks = [(k, v[0], v[1], v[2]) for k, v in handler.db.tasks.items()] + tasks = [(k, v[0], v[1], v[2]) for k, v in list(handler.db.tasks.items())] if obj: tasks = [task for task in tasks if task[2] is obj] if callback_name: @@ -518,7 +518,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS): delta = time_format((future - now).total_seconds(), 1) table.add_row(task_id, key, callback_name, delta) - self.msg(unicode(table)) + self.msg(str(table)) # Private functions to handle editing diff --git a/evennia/contrib/ingame_python/scripts.py b/evennia/contrib/ingame_python/scripts.py index 8ffdf172a3..097923878f 100644 --- a/evennia/contrib/ingame_python/scripts.py +++ b/evennia/contrib/ingame_python/scripts.py @@ -3,7 +3,7 @@ Scripts for the in-game Python system. """ from datetime import datetime, timedelta -from Queue import Queue +from queue import Queue import re import sys import traceback @@ -129,7 +129,7 @@ class EventHandler(DefaultScript): while not classes.empty(): typeclass = classes.get() typeclass_name = typeclass.__module__ + "." + typeclass.__name__ - for key, etype in all_events.get(typeclass_name, {}).items(): + for key, etype in list(all_events.get(typeclass_name, {}).items()): if key in invalid: continue if etype[0] is None: # Invalidate @@ -186,7 +186,7 @@ class EventHandler(DefaultScript): """ obj_callbacks = self.db.callbacks.get(obj, {}) callbacks = {} - for callback_name, callback_list in obj_callbacks.items(): + for callback_name, callback_list in list(obj_callbacks.items()): new_list = [] for i, callback in enumerate(callback_list): callback = dict(callback) @@ -436,7 +436,7 @@ class EventHandler(DefaultScript): type(obj), variable, i)) return False else: - locals = {key: value for key, value in locals.items()} + locals = {key: value for key, value in list(locals.items())} callbacks = self.get_callbacks(obj).get(callback_name, []) if event: @@ -576,7 +576,7 @@ class EventHandler(DefaultScript): # Collect and freeze current locals locals = {} - for key, value in self.ndb.current_locals.items(): + for key, value in list(self.ndb.current_locals.items()): try: dbserialize(value) except TypeError: diff --git a/evennia/contrib/ingame_python/tests.py b/evennia/contrib/ingame_python/tests.py index 496c93944d..15a8a45dad 100644 --- a/evennia/contrib/ingame_python/tests.py +++ b/evennia/contrib/ingame_python/tests.py @@ -224,13 +224,13 @@ class TestEventHandler(EvenniaTest): self.assertEqual(callback.code, "pass") self.assertEqual(callback.author, self.char1) self.assertEqual(callback.valid, True) - self.assertIn([callback], self.room1.callbacks.all().values()) + self.assertIn([callback], list(self.room1.callbacks.all().values())) # Edit this very callback new = self.room1.callbacks.edit("dummy", 0, "character.db.say = True", author=self.char1, valid=True) - self.assertIn([new], self.room1.callbacks.all().values()) - self.assertNotIn([callback], self.room1.callbacks.all().values()) + self.assertIn([new], list(self.room1.callbacks.all().values())) + self.assertNotIn([callback], list(self.room1.callbacks.all().values())) # Try to call this callback self.assertTrue(self.room1.callbacks.call("dummy", diff --git a/evennia/contrib/ingame_python/utils.py b/evennia/contrib/ingame_python/utils.py index 71a7c9d4c5..47314f8a88 100644 --- a/evennia/contrib/ingame_python/utils.py +++ b/evennia/contrib/ingame_python/utils.py @@ -50,7 +50,7 @@ def register_events(path_or_typeclass): temporary storage, waiting for the script to be initialized. """ - if isinstance(path_or_typeclass, basestring): + if isinstance(path_or_typeclass, str): typeclass = class_from_module(path_or_typeclass) else: typeclass = path_or_typeclass @@ -65,7 +65,7 @@ def register_events(path_or_typeclass): # If the script is started, add the event directly. # Otherwise, add it to the temporary storage. - for name, tup in getattr(typeclass, "_events", {}).items(): + for name, tup in list(getattr(typeclass, "_events", {}).items()): if len(tup) == 4: variables, help_text, custom_call, custom_add = tup elif len(tup) == 3: @@ -116,7 +116,7 @@ def get_next_wait(format): units = ["min", "hour", "day", "month", "year"] elif calendar == "custom": rsu = custom_rsu - back = dict([(value, name) for name, value in UNITS.items()]) + back = dict([(value, name) for name, value in list(UNITS.items())]) sorted_units = sorted(back.items()) del sorted_units[0] units = [n for v, n in sorted_units] diff --git a/evennia/contrib/mail.py b/evennia/contrib/mail.py index 6e8585136d..a4aee2a4b1 100644 --- a/evennia/contrib/mail.py +++ b/evennia/contrib/mail.py @@ -259,7 +259,7 @@ class CmdMail(default_cmds.MuxCommand): table.reformat_column(4, width=7) self.caller.msg(_HEAD_CHAR * _WIDTH) - self.caller.msg(unicode(table)) + self.caller.msg(str(table)) self.caller.msg(_HEAD_CHAR * _WIDTH) else: self.caller.msg("There are no messages in your inbox.") diff --git a/evennia/contrib/mapbuilder.py b/evennia/contrib/mapbuilder.py index 2b156eb469..3bbf5c72d1 100644 --- a/evennia/contrib/mapbuilder.py +++ b/evennia/contrib/mapbuilder.py @@ -139,7 +139,7 @@ def example1_build_mountains(x, y, **kwargs): room.db.desc = random.choice(room_desc) # Create a random number of objects to populate the room. - for i in xrange(randint(0, 3)): + for i in range(randint(0, 3)): rock = create_object(key="Rock", location=room) rock.db.desc = "An ordinary rock." @@ -286,7 +286,7 @@ def _map_to_list(game_map): """ list_map = game_map.split('\n') - return [character.decode('UTF-8') if isinstance(character, basestring) + return [character.decode('UTF-8') if isinstance(character, str) else character for character in list_map] @@ -321,9 +321,9 @@ def build_map(caller, game_map, legend, iterations=1, build_exits=True): room_dict = {} caller.msg("Creating Landmass...") - for iteration in xrange(iterations): - for y in xrange(len(game_map)): - for x in xrange(len(game_map[y])): + for iteration in range(iterations): + for y in range(len(game_map)): + for x in range(len(game_map[y])): for key in legend: # obs - we must use == for unicode if utils.to_unicode(game_map[y][x]) == utils.to_unicode(key): @@ -336,7 +336,7 @@ def build_map(caller, game_map, legend, iterations=1, build_exits=True): if build_exits: # Creating exits. Assumes single room object in dict entry caller.msg("Connecting Areas...") - for loc_key, location in room_dict.iteritems(): + for loc_key, location in room_dict.items(): x = loc_key[0] y = loc_key[1] diff --git a/evennia/contrib/rplanguage.py b/evennia/contrib/rplanguage.py index 2159719641..4784bb1c08 100644 --- a/evennia/contrib/rplanguage.py +++ b/evennia/contrib/rplanguage.py @@ -232,7 +232,7 @@ class LanguageHandler(DefaultScript): translation = {} if auto_translations: - if isinstance(auto_translations, basestring): + if isinstance(auto_translations, str): # path to a file rather than a list with open(auto_translations, 'r') as f: auto_translations = f.readlines() @@ -254,7 +254,7 @@ class LanguageHandler(DefaultScript): if manual_translations: # update with manual translations - translation.update(dict((key.lower(), value.lower()) for key, value in manual_translations.items())) + translation.update(dict((key.lower(), value.lower()) for key, value in list(manual_translations.items()))) # store data storage = {"translation": translation, diff --git a/evennia/contrib/rpsystem.py b/evennia/contrib/rpsystem.py index a396a18abc..e3ea6b84e4 100644 --- a/evennia/contrib/rpsystem.py +++ b/evennia/contrib/rpsystem.py @@ -511,7 +511,7 @@ def send_emote(sender, receivers, emote, anonymous_add="first"): process_language = receiver.process_language except AttributeError: process_language = _dummy_process - for key, (langname, saytext) in language_mapping.iteritems(): + for key, (langname, saytext) in language_mapping.items(): # color says receiver_lang_mapping[key] = process_language(saytext, sender, langname) # map the language {##num} markers. This will convert the escaped sdesc markers on @@ -531,11 +531,11 @@ def send_emote(sender, receivers, emote, anonymous_add="first"): try: recog_get = receiver.recog.get - receiver_sdesc_mapping = dict((ref, process_recog(recog_get(obj), obj)) for ref, obj in obj_mapping.items()) + receiver_sdesc_mapping = dict((ref, process_recog(recog_get(obj), obj)) for ref, obj in list(obj_mapping.items())) except AttributeError: receiver_sdesc_mapping = dict((ref, process_sdesc(obj.sdesc.get(), obj) if hasattr(obj, "sdesc") else process_sdesc(obj.key, obj)) - for ref, obj in obj_mapping.items()) + for ref, obj in list(obj_mapping.items())) # make sure receiver always sees their real name rkey = "#%i" % receiver.id if rkey in receiver_sdesc_mapping: @@ -684,9 +684,9 @@ class RecogHandler(object): obj2regex = self.obj.attributes.get("_recog_obj2regex", default={}) obj2recog = self.obj.attributes.get("_recog_obj2recog", default={}) self.obj2regex = dict((obj, re.compile(regex, _RE_FLAGS)) - for obj, regex in obj2regex.items() if obj) + for obj, regex in list(obj2regex.items()) if obj) self.obj2recog = dict((obj, recog) - for obj, recog in obj2recog.items() if obj) + for obj, recog in list(obj2recog.items()) if obj) def add(self, obj, recog, max_length=60): """ @@ -981,7 +981,7 @@ class CmdPose(RPCommand): # set current pose and default pose # set the pose. We do one-time ref->sdesc mapping here. parsed, mapping = parse_sdescs_and_recogs(caller, caller.location.contents, pose) mapping = dict((ref, obj.sdesc.get() if hasattr(obj, "sdesc") else obj.key) - for ref, obj in mapping.iteritems()) + for ref, obj in mapping.items()) pose = parsed.format(**mapping) if len(target_name) + len(pose) > 60: @@ -1223,7 +1223,7 @@ class ContribRPObject(DefaultObject): messaging is assumed to be handled by the caller. """ - is_string = isinstance(searchdata, basestring) + is_string = isinstance(searchdata, str) if is_string: # searchdata is a string; wrap some common self-references diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 1fdd7dde75..cfb7d59ead 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -355,14 +355,14 @@ class TestWilderness(EvenniaTest): wilderness.enter_wilderness(self.char1) self.assertIsInstance(self.char1.location, wilderness.WildernessRoom) w = self.get_wilderness_script() - self.assertEquals(w.db.itemcoordinates[self.char1], (0, 0)) + self.assertEqual(w.db.itemcoordinates[self.char1], (0, 0)) def test_enter_wilderness_custom_coordinates(self): wilderness.create_wilderness() wilderness.enter_wilderness(self.char1, coordinates=(1, 2)) self.assertIsInstance(self.char1.location, wilderness.WildernessRoom) w = self.get_wilderness_script() - self.assertEquals(w.db.itemcoordinates[self.char1], (1, 2)) + self.assertEqual(w.db.itemcoordinates[self.char1], (1, 2)) def test_enter_wilderness_custom_name(self): name = "customnname" @@ -381,7 +381,7 @@ class TestWilderness(EvenniaTest): i.access(self.char1, "view") or i.access(self.char1, "traverse"))] - self.assertEquals(len(exits), 3) + self.assertEqual(len(exits), 3) exitsok = ["north", "northeast", "east"] for each_exit in exitsok: self.assertTrue(any([e for e in exits if e.key == each_exit])) @@ -393,7 +393,7 @@ class TestWilderness(EvenniaTest): if i.destination and ( i.access(self.char1, "view") or i.access(self.char1, "traverse"))] - self.assertEquals(len(exits), 8) + self.assertEqual(len(exits), 8) exitsok = ["north", "northeast", "east", "southeast", "south", "southwest", "west", "northwest"] for each_exit in exitsok: @@ -410,25 +410,25 @@ class TestWilderness(EvenniaTest): w = self.get_wilderness_script() # We should have no unused room after moving the first account in. - self.assertEquals(len(w.db.unused_rooms), 0) + self.assertEqual(len(w.db.unused_rooms), 0) w.move_obj(self.char1, (0, 0)) - self.assertEquals(len(w.db.unused_rooms), 0) + self.assertEqual(len(w.db.unused_rooms), 0) # And also no unused room after moving the second one in. w.move_obj(self.char2, (1, 1)) - self.assertEquals(len(w.db.unused_rooms), 0) + self.assertEqual(len(w.db.unused_rooms), 0) # But if char2 moves into char1's room, we should have one unused room # Which should be char2's old room that got created. w.move_obj(self.char2, (0, 0)) - self.assertEquals(len(w.db.unused_rooms), 1) - self.assertEquals(self.char1.location, self.char2.location) + self.assertEqual(len(w.db.unused_rooms), 1) + self.assertEqual(self.char1.location, self.char2.location) # And if char2 moves back out, that unused room should be put back to # use again. w.move_obj(self.char2, (1, 1)) - self.assertNotEquals(self.char1.location, self.char2.location) - self.assertEquals(len(w.db.unused_rooms), 0) + self.assertNotEqual(self.char1.location, self.char2.location) + self.assertEqual(len(w.db.unused_rooms), 0) def test_get_new_coordinates(self): loc = (1, 1) @@ -440,9 +440,9 @@ class TestWilderness(EvenniaTest): "southwest": (0, 0), "west": (0, 1), "northwest": (0, 2)} - for direction, correct_loc in directions.iteritems(): # Not compatible with Python 3 + for direction, correct_loc in directions.items(): # Not compatible with Python 3 new_loc = wilderness.get_new_coordinates(loc, direction) - self.assertEquals(new_loc, correct_loc, direction) + self.assertEqual(new_loc, correct_loc, direction) # Testing chargen contrib diff --git a/evennia/contrib/tutorial_world/__init__.py b/evennia/contrib/tutorial_world/__init__.py index 87f7c7f4cb..45bded2779 100644 --- a/evennia/contrib/tutorial_world/__init__.py +++ b/evennia/contrib/tutorial_world/__init__.py @@ -2,6 +2,6 @@ """ This package holds the demo game of Evennia. """ -from __future__ import absolute_import + from . import mob, objects, rooms diff --git a/evennia/contrib/tutorial_world/objects.py b/evennia/contrib/tutorial_world/objects.py index 3859de7d2d..1e836d5bb1 100644 --- a/evennia/contrib/tutorial_world/objects.py +++ b/evennia/contrib/tutorial_world/objects.py @@ -689,7 +689,7 @@ class CrumblingWall(TutorialObject, DefaultExit): "crisscross the wall, making it hard to clearly see its stony surface. Maybe you could " "try to |wshift|n or |wmove|n them.\n"] # display the root positions to help with the puzzle - for key, pos in self.db.root_pos.items(): + for key, pos in list(self.db.root_pos.items()): result.append("\n" + self._translate_position(key, pos)) self.db.desc = "".join(result) diff --git a/evennia/contrib/tutorial_world/rooms.py b/evennia/contrib/tutorial_world/rooms.py index c16baccff1..e780124609 100644 --- a/evennia/contrib/tutorial_world/rooms.py +++ b/evennia/contrib/tutorial_world/rooms.py @@ -8,7 +8,7 @@ commands needed to control them. Those commands could also have been in a separate module (e.g. if they could have been re-used elsewhere.) """ -from __future__ import print_function + import random from evennia import TICKER_HANDLER diff --git a/evennia/contrib/wilderness.py b/evennia/contrib/wilderness.py index f36dba492d..6d6ebf6649 100644 --- a/evennia/contrib/wilderness.py +++ b/evennia/contrib/wilderness.py @@ -249,10 +249,10 @@ class WildernessScript(DefaultScript): """ Called when the script is started and also after server reloads. """ - for coordinates, room in self.db.rooms.items(): + for coordinates, room in list(self.db.rooms.items()): room.ndb.wildernessscript = self room.ndb.active_coordinates = coordinates - for item in self.db.itemcoordinates.keys(): + for item in list(self.db.itemcoordinates.keys()): # Items deleted from the wilderness leave None type 'ghosts' # that must be cleaned out if item is None: @@ -302,7 +302,7 @@ class WildernessScript(DefaultScript): [Object, ]: list of Objects at coordinates """ result = [] - for item, item_coordinates in self.itemcoordinates.items(): + for item, item_coordinates in list(self.itemcoordinates.items()): # Items deleted from the wilderness leave None type 'ghosts' # that must be cleaned out if item is None: diff --git a/evennia/game_template/server/conf/settings.py b/evennia/game_template/server/conf/settings.py index 7fe163b833..fa314c977a 100644 --- a/evennia/game_template/server/conf/settings.py +++ b/evennia/game_template/server/conf/settings.py @@ -62,4 +62,4 @@ AMP_PORT = 4006 try: from server.conf.secret_settings import * except ImportError: - print "secret_settings.py file not found or failed to import." + print("secret_settings.py file not found or failed to import.") diff --git a/evennia/help/migrations/0001_initial.py b/evennia/help/migrations/0001_initial.py index a8bc6fc91e..6bc0be6b97 100644 --- a/evennia/help/migrations/0001_initial.py +++ b/evennia/help/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/evennia/help/migrations/0002_auto_20170606_1731.py b/evennia/help/migrations/0002_auto_20170606_1731.py index 65ab4a5ee1..10f939c18d 100644 --- a/evennia/help/migrations/0002_auto_20170606_1731.py +++ b/evennia/help/migrations/0002_auto_20170606_1731.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.2 on 2017-06-06 17:31 -from __future__ import unicode_literals + from django.db import migrations, models diff --git a/evennia/help/models.py b/evennia/help/models.py index 4a81a94099..b19c551f25 100644 --- a/evennia/help/models.py +++ b/evennia/help/models.py @@ -97,7 +97,7 @@ class HelpEntry(SharedMemoryModel): return self.key def __unicode__(self): - return u'%s' % self.key + return '%s' % self.key def access(self, accessing_obj, access_type='read', default=False): """ diff --git a/evennia/locks/lockfuncs.py b/evennia/locks/lockfuncs.py index e30263adbf..c39c82a350 100644 --- a/evennia/locks/lockfuncs.py +++ b/evennia/locks/lockfuncs.py @@ -87,7 +87,7 @@ DefaultLock: Exits: controls who may traverse the exit to Dark/light script ``` """ -from __future__ import print_function + from django.conf import settings from evennia.utils import utils diff --git a/evennia/locks/lockhandler.py b/evennia/locks/lockhandler.py index b8801f9655..3d1787ad9d 100644 --- a/evennia/locks/lockhandler.py +++ b/evennia/locks/lockhandler.py @@ -103,7 +103,7 @@ restricted @perm command sets them, but otherwise they are identical to any other identifier you can use. """ -from __future__ import print_function + from builtins import object import re @@ -269,7 +269,7 @@ class LockHandler(object): """ Store locks to obj """ - self.obj.lock_storage = ";".join([tup[2] for tup in self.locks.values()]) + self.obj.lock_storage = ";".join([tup[2] for tup in list(self.locks.values())]) def cache_lock_bypass(self, obj): """ @@ -302,7 +302,7 @@ class LockHandler(object): error. """ - if isinstance(lockstring, basestring): + if isinstance(lockstring, str): lockdefs = lockstring.split(";") else: lockdefs = [lockdef for locks in lockstring for lockdef in locks.split(";")] diff --git a/evennia/locks/tests.py b/evennia/locks/tests.py index 0c47456ede..29b5810126 100644 --- a/evennia/locks/tests.py +++ b/evennia/locks/tests.py @@ -27,29 +27,29 @@ class TestLockCheck(EvenniaTest): dbref = self.obj2.dbref self.obj1.locks.add("owner:dbref(%s);edit:dbref(%s) or perm(Admin);examine:perm(Builder) and id(%s);delete:perm(Admin);get:all()" % (dbref, dbref, dbref)) self.obj2.permissions.add('Admin') - self.assertEquals(True, self.obj1.locks.check(self.obj2, 'owner')) - self.assertEquals(True, self.obj1.locks.check(self.obj2, 'edit')) - self.assertEquals(True, self.obj1.locks.check(self.obj2, 'examine')) - self.assertEquals(True, self.obj1.locks.check(self.obj2, 'delete')) - self.assertEquals(True, self.obj1.locks.check(self.obj2, 'get')) + self.assertEqual(True, self.obj1.locks.check(self.obj2, 'owner')) + self.assertEqual(True, self.obj1.locks.check(self.obj2, 'edit')) + self.assertEqual(True, self.obj1.locks.check(self.obj2, 'examine')) + self.assertEqual(True, self.obj1.locks.check(self.obj2, 'delete')) + self.assertEqual(True, self.obj1.locks.check(self.obj2, 'get')) self.obj1.locks.add("get:false()") - self.assertEquals(False, self.obj1.locks.check(self.obj2, 'get')) - self.assertEquals(True, self.obj1.locks.check(self.obj2, 'not_exist', default=True)) + self.assertEqual(False, self.obj1.locks.check(self.obj2, 'get')) + self.assertEqual(True, self.obj1.locks.check(self.obj2, 'not_exist', default=True)) class TestLockfuncs(EvenniaTest): def testrun(self): self.obj2.permissions.add('Admin') - self.assertEquals(True, lockfuncs.true(self.obj2, self.obj1)) - self.assertEquals(False, lockfuncs.false(self.obj2, self.obj1)) - self.assertEquals(True, lockfuncs.perm(self.obj2, self.obj1, 'Admin')) - self.assertEquals(True, lockfuncs.perm_above(self.obj2, self.obj1, 'Builder')) + self.assertEqual(True, lockfuncs.true(self.obj2, self.obj1)) + self.assertEqual(False, lockfuncs.false(self.obj2, self.obj1)) + self.assertEqual(True, lockfuncs.perm(self.obj2, self.obj1, 'Admin')) + self.assertEqual(True, lockfuncs.perm_above(self.obj2, self.obj1, 'Builder')) dbref = self.obj2.dbref - self.assertEquals(True, lockfuncs.dbref(self.obj2, self.obj1, '%s' % dbref)) + self.assertEqual(True, lockfuncs.dbref(self.obj2, self.obj1, '%s' % dbref)) self.obj2.db.testattr = 45 - self.assertEquals(True, lockfuncs.attr(self.obj2, self.obj1, 'testattr', '45')) - self.assertEquals(False, lockfuncs.attr_gt(self.obj2, self.obj1, 'testattr', '45')) - self.assertEquals(True, lockfuncs.attr_ge(self.obj2, self.obj1, 'testattr', '45')) - self.assertEquals(False, lockfuncs.attr_lt(self.obj2, self.obj1, 'testattr', '45')) - self.assertEquals(True, lockfuncs.attr_le(self.obj2, self.obj1, 'testattr', '45')) - self.assertEquals(False, lockfuncs.attr_ne(self.obj2, self.obj1, 'testattr', '45')) + self.assertEqual(True, lockfuncs.attr(self.obj2, self.obj1, 'testattr', '45')) + self.assertEqual(False, lockfuncs.attr_gt(self.obj2, self.obj1, 'testattr', '45')) + self.assertEqual(True, lockfuncs.attr_ge(self.obj2, self.obj1, 'testattr', '45')) + self.assertEqual(False, lockfuncs.attr_lt(self.obj2, self.obj1, 'testattr', '45')) + self.assertEqual(True, lockfuncs.attr_le(self.obj2, self.obj1, 'testattr', '45')) + self.assertEqual(False, lockfuncs.attr_ne(self.obj2, self.obj1, 'testattr', '45')) diff --git a/evennia/objects/manager.py b/evennia/objects/manager.py index 3d29768e5a..22ef174636 100644 --- a/evennia/objects/manager.py +++ b/evennia/objects/manager.py @@ -151,7 +151,7 @@ class ObjectDBManager(TypedObjectManager): # This doesn't work if attribute_value is an object. Workaround below - if isinstance(attribute_value, (basestring, int, float, bool)): + if isinstance(attribute_value, (str, int, float, bool)): return self.filter(cand_restriction & type_restriction & Q(db_attributes__db_key=attribute_name, db_attributes__db_value=attribute_value)) else: @@ -196,9 +196,9 @@ class ObjectDBManager(TypedObjectManager): typeclasses (list, optional): List of typeclass-path strings to restrict matches with """ - if isinstance(property_value, basestring): + if isinstance(property_value, str): property_value = to_unicode(property_value) - if isinstance(property_name, basestring): + if isinstance(property_name, str): if not property_name.startswith('db_'): property_name = "db_%s" % property_name querykwargs = {property_name: property_value} @@ -244,7 +244,7 @@ class ObjectDBManager(TypedObjectManager): Returns: matches (list): A list of matches of length 0, 1 or more. """ - if not isinstance(ostring, basestring): + if not isinstance(ostring, str): if hasattr(ostring, "key"): ostring = ostring.key else: @@ -365,9 +365,9 @@ class ObjectDBManager(TypedObjectManager): typeclasses = make_iter(typeclass) for i, typeclass in enumerate(make_iter(typeclasses)): if callable(typeclass): - typeclasses[i] = u"%s.%s" % (typeclass.__module__, typeclass.__name__) + typeclasses[i] = "%s.%s" % (typeclass.__module__, typeclass.__name__) else: - typeclasses[i] = u"%s" % typeclass + typeclasses[i] = "%s" % typeclass typeclass = typeclasses if candidates is not None: diff --git a/evennia/objects/migrations/0001_initial.py b/evennia/objects/migrations/0001_initial.py index bba205bffb..ac528b9fd1 100644 --- a/evennia/objects/migrations/0001_initial.py +++ b/evennia/objects/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations import django.db.models.deletion diff --git a/evennia/objects/migrations/0002_auto_20140917_0756.py b/evennia/objects/migrations/0002_auto_20140917_0756.py index 7483c27268..916671f38e 100644 --- a/evennia/objects/migrations/0002_auto_20140917_0756.py +++ b/evennia/objects/migrations/0002_auto_20140917_0756.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations import django.db.models.deletion diff --git a/evennia/objects/migrations/0003_defaultcharacter_defaultexit_defaultobject_defaultroom.py b/evennia/objects/migrations/0003_defaultcharacter_defaultexit_defaultobject_defaultroom.py index fd72933e43..c69ac070d1 100644 --- a/evennia/objects/migrations/0003_defaultcharacter_defaultexit_defaultobject_defaultroom.py +++ b/evennia/objects/migrations/0003_defaultcharacter_defaultexit_defaultobject_defaultroom.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/evennia/objects/migrations/0004_auto_20150118_1622.py b/evennia/objects/migrations/0004_auto_20150118_1622.py index 4626b63c94..640b5e9386 100644 --- a/evennia/objects/migrations/0004_auto_20150118_1622.py +++ b/evennia/objects/migrations/0004_auto_20150118_1622.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/evennia/objects/migrations/0005_auto_20150403_2339.py b/evennia/objects/migrations/0005_auto_20150403_2339.py index d8359a909d..4b0521128e 100644 --- a/evennia/objects/migrations/0005_auto_20150403_2339.py +++ b/evennia/objects/migrations/0005_auto_20150403_2339.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/evennia/objects/migrations/0006_auto_20170606_1731.py b/evennia/objects/migrations/0006_auto_20170606_1731.py index df56b38745..daf46ed329 100644 --- a/evennia/objects/migrations/0006_auto_20170606_1731.py +++ b/evennia/objects/migrations/0006_auto_20170606_1731.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.2 on 2017-06-06 17:31 -from __future__ import unicode_literals + import django.core.validators from django.db import migrations, models diff --git a/evennia/objects/migrations/0007_objectdb_db_account.py b/evennia/objects/migrations/0007_objectdb_db_account.py index 6e40252e8e..b91e315b49 100644 --- a/evennia/objects/migrations/0007_objectdb_db_account.py +++ b/evennia/objects/migrations/0007_objectdb_db_account.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.2 on 2017-07-05 17:27 -from __future__ import unicode_literals + from django.db import migrations, models, connection import django.db.models.deletion diff --git a/evennia/objects/migrations/0008_auto_20170705_1736.py b/evennia/objects/migrations/0008_auto_20170705_1736.py index c2cd711aff..355a339910 100644 --- a/evennia/objects/migrations/0008_auto_20170705_1736.py +++ b/evennia/objects/migrations/0008_auto_20170705_1736.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.2 on 2017-07-05 17:36 -from __future__ import unicode_literals + from django.db import migrations diff --git a/evennia/objects/migrations/0009_remove_objectdb_db_player.py b/evennia/objects/migrations/0009_remove_objectdb_db_player.py index 10fb2252f4..403b072dfe 100644 --- a/evennia/objects/migrations/0009_remove_objectdb_db_player.py +++ b/evennia/objects/migrations/0009_remove_objectdb_db_player.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.2 on 2017-07-06 20:41 -from __future__ import unicode_literals + from django.db import migrations, connection diff --git a/evennia/objects/models.py b/evennia/objects/models.py index adac6e0a5a..884cc28d80 100644 --- a/evennia/objects/models.py +++ b/evennia/objects/models.py @@ -231,7 +231,7 @@ class ObjectDB(TypedObject): def __location_set(self, location): """Set location, checking for loops and allowing dbref""" - if isinstance(location, (basestring, int)): + if isinstance(location, (str, int)): # allow setting of #dbref dbid = dbref(location, reqhash=False) if dbid: diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index e0fd0dace4..23e65c31bf 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -359,7 +359,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): messaging is assumed to be handled by the caller. """ - is_string = isinstance(searchdata, basestring) + is_string = isinstance(searchdata, str) if is_string: @@ -436,7 +436,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): matching Accounts. """ - if isinstance(searchdata, basestring): + if isinstance(searchdata, str): # searchdata is a string; wrap some common self-references if searchdata.lower() in ("me", "self",): return [self.account] if quiet else self.account @@ -615,7 +615,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): if mapping: substitutions = {t: sub.get_display_name(obj) if hasattr(sub, 'get_display_name') - else str(sub) for t, sub in mapping.items()} + else str(sub) for t, sub in list(mapping.items())} outmessage = inmessage.format(**substitutions) else: outmessage = inmessage @@ -959,7 +959,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): self.attributes.batch_add(*cdict["attributes"]) if cdict.get("nattributes"): # this should be a dict of nattrname:value - for key, value in cdict["nattributes"].items(): + for key, value in list(cdict["nattributes"].items()): self.nattributes.add(key, value) del self._createdict diff --git a/evennia/scripts/manager.py b/evennia/scripts/manager.py index 1b795b2c3e..8d67b6f966 100644 --- a/evennia/scripts/manager.py +++ b/evennia/scripts/manager.py @@ -240,9 +240,9 @@ class ScriptDBManager(TypedObjectManager): if typeclass: if callable(typeclass): - typeclass = u"%s.%s" % (typeclass.__module__, typeclass.__name__) + typeclass = "%s.%s" % (typeclass.__module__, typeclass.__name__) else: - typeclass = u"%s" % typeclass + typeclass = "%s" % typeclass # not a dbref; normal search obj_restriction = obj and Q(db_obj=obj) or Q() diff --git a/evennia/scripts/migrations/0001_initial.py b/evennia/scripts/migrations/0001_initial.py index 751bd2928e..385e3901e0 100644 --- a/evennia/scripts/migrations/0001_initial.py +++ b/evennia/scripts/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations from django.conf import settings diff --git a/evennia/scripts/migrations/0002_auto_20150118_1625.py b/evennia/scripts/migrations/0002_auto_20150118_1625.py index 272c9b73d0..0022111e2b 100644 --- a/evennia/scripts/migrations/0002_auto_20150118_1625.py +++ b/evennia/scripts/migrations/0002_auto_20150118_1625.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/evennia/scripts/migrations/0003_checksessions_defaultscript_donothing_scriptbase_store_validatechannelhandler_validateidmappercache_.py b/evennia/scripts/migrations/0003_checksessions_defaultscript_donothing_scriptbase_store_validatechannelhandler_validateidmappercache_.py index baa3b0d2ee..3d80e56985 100644 --- a/evennia/scripts/migrations/0003_checksessions_defaultscript_donothing_scriptbase_store_validatechannelhandler_validateidmappercache_.py +++ b/evennia/scripts/migrations/0003_checksessions_defaultscript_donothing_scriptbase_store_validatechannelhandler_validateidmappercache_.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/evennia/scripts/migrations/0004_auto_20150306_1354.py b/evennia/scripts/migrations/0004_auto_20150306_1354.py index 72bd29a4e7..0c4310ee3d 100644 --- a/evennia/scripts/migrations/0004_auto_20150306_1354.py +++ b/evennia/scripts/migrations/0004_auto_20150306_1354.py @@ -1,16 +1,16 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations def remove_manage_scripts(apps, schema_editor): ScriptDB = apps.get_model("scripts", "ScriptDB") - for script in ScriptDB.objects.filter(db_typeclass_path__in=(u'evennia.scripts.scripts.CheckSessions', - u'evennia.scripts.scripts.ValidateScripts', - u'evennia.scripts.scripts.ValidateChannelHandler', - u'evennia.scripts.scripts.ValidateIdmapperCache', - u'evennia.utils.gametime.GameTime')): + for script in ScriptDB.objects.filter(db_typeclass_path__in=('evennia.scripts.scripts.CheckSessions', + 'evennia.scripts.scripts.ValidateScripts', + 'evennia.scripts.scripts.ValidateChannelHandler', + 'evennia.scripts.scripts.ValidateIdmapperCache', + 'evennia.utils.gametime.GameTime')): script.delete() diff --git a/evennia/scripts/migrations/0005_auto_20150306_1441.py b/evennia/scripts/migrations/0005_auto_20150306_1441.py index 92ab743557..b800c80553 100644 --- a/evennia/scripts/migrations/0005_auto_20150306_1441.py +++ b/evennia/scripts/migrations/0005_auto_20150306_1441.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/evennia/scripts/migrations/0006_auto_20150310_2249.py b/evennia/scripts/migrations/0006_auto_20150310_2249.py index d027d49007..80cc25bee0 100644 --- a/evennia/scripts/migrations/0006_auto_20150310_2249.py +++ b/evennia/scripts/migrations/0006_auto_20150310_2249.py @@ -1,16 +1,16 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations def remove_manage_scripts(apps, schema_editor): ScriptDB = apps.get_model("scripts", "ScriptDB") - for script in ScriptDB.objects.filter(db_typeclass_path__in=(u'src.scripts.scripts.CheckSessions', - u'src.scripts.scripts.ValidateScripts', - u'src.scripts.scripts.ValidateChannelHandler', - u'src.scripts.scripts.ValidateIdmapperCache', - u'src.utils.gametime.GameTime')): + for script in ScriptDB.objects.filter(db_typeclass_path__in=('src.scripts.scripts.CheckSessions', + 'src.scripts.scripts.ValidateScripts', + 'src.scripts.scripts.ValidateChannelHandler', + 'src.scripts.scripts.ValidateIdmapperCache', + 'src.utils.gametime.GameTime')): script.delete() diff --git a/evennia/scripts/migrations/0007_auto_20150403_2339.py b/evennia/scripts/migrations/0007_auto_20150403_2339.py index 91fb359dc3..530a93ebb9 100644 --- a/evennia/scripts/migrations/0007_auto_20150403_2339.py +++ b/evennia/scripts/migrations/0007_auto_20150403_2339.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/evennia/scripts/migrations/0008_auto_20170606_1731.py b/evennia/scripts/migrations/0008_auto_20170606_1731.py index b4a7f3201c..d8d7700f4e 100644 --- a/evennia/scripts/migrations/0008_auto_20170606_1731.py +++ b/evennia/scripts/migrations/0008_auto_20170606_1731.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.2 on 2017-06-06 17:31 -from __future__ import unicode_literals + from django.db import migrations, models diff --git a/evennia/scripts/migrations/0009_scriptdb_db_account.py b/evennia/scripts/migrations/0009_scriptdb_db_account.py index 23f6df92c1..2ad24bf7e2 100644 --- a/evennia/scripts/migrations/0009_scriptdb_db_account.py +++ b/evennia/scripts/migrations/0009_scriptdb_db_account.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.2 on 2017-07-05 17:27 -from __future__ import unicode_literals + from django.db import migrations, models, connection import django.db.models.deletion diff --git a/evennia/scripts/migrations/0010_auto_20170705_1736.py b/evennia/scripts/migrations/0010_auto_20170705_1736.py index 61c9e929ae..c3fbddcafb 100644 --- a/evennia/scripts/migrations/0010_auto_20170705_1736.py +++ b/evennia/scripts/migrations/0010_auto_20170705_1736.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.2 on 2017-07-05 17:36 -from __future__ import unicode_literals + from django.db import migrations diff --git a/evennia/scripts/migrations/0011_remove_scriptdb_db_player.py b/evennia/scripts/migrations/0011_remove_scriptdb_db_player.py index 20fa63fd50..e5ed4bc915 100644 --- a/evennia/scripts/migrations/0011_remove_scriptdb_db_player.py +++ b/evennia/scripts/migrations/0011_remove_scriptdb_db_player.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.2 on 2017-07-06 20:41 -from __future__ import unicode_literals + from django.db import migrations, connection diff --git a/evennia/scripts/models.py b/evennia/scripts/models.py index d470349964..51fa55cbb7 100644 --- a/evennia/scripts/models.py +++ b/evennia/scripts/models.py @@ -141,7 +141,7 @@ class ScriptDB(TypedObject): except AttributeError: # deprecated ... pass - if isinstance(value, (basestring, int)): + if isinstance(value, (str, int)): from evennia.objects.models import ObjectDB value = to_str(value, force_string=True) if (value.isdigit() or value.startswith("#")): diff --git a/evennia/scripts/monitorhandler.py b/evennia/scripts/monitorhandler.py index af6ae4fd61..c3027ff773 100644 --- a/evennia/scripts/monitorhandler.py +++ b/evennia/scripts/monitorhandler.py @@ -50,8 +50,8 @@ class MonitorHandler(object): if self.monitors: for obj in self.monitors: for fieldname in self.monitors[obj]: - for idstring, (callback, persistent, kwargs) in self.monitors[obj][fieldname].iteritems(): - path = "%s.%s" % (callback.__module__, callback.func_name) + for idstring, (callback, persistent, kwargs) in self.monitors[obj][fieldname].items(): + path = "%s.%s" % (callback.__module__, callback.__name__) savedata.append((obj, fieldname, idstring, path, persistent, kwargs)) savedata = dbserialize(savedata) ServerConfig.objects.conf(key=self.savekey, value=savedata) @@ -97,7 +97,7 @@ class MonitorHandler(object): """ to_delete = [] if obj in self.monitors and fieldname in self.monitors[obj]: - for idstring, (callback, persistent, kwargs) in self.monitors[obj][fieldname].iteritems(): + for idstring, (callback, persistent, kwargs) in self.monitors[obj][fieldname].items(): try: callback(obj=obj, fieldname=fieldname, **kwargs) except Exception: @@ -183,7 +183,7 @@ class MonitorHandler(object): output = [] for obj in self.monitors: for fieldname in self.monitors[obj]: - for idstring, (callback, persistent, kwargs) in self.monitors[obj][fieldname].iteritems(): + for idstring, (callback, persistent, kwargs) in self.monitors[obj][fieldname].items(): output.append((obj, fieldname, idstring, persistent, kwargs)) return output diff --git a/evennia/scripts/taskhandler.py b/evennia/scripts/taskhandler.py index f4819ba076..f4f94e75d0 100644 --- a/evennia/scripts/taskhandler.py +++ b/evennia/scripts/taskhandler.py @@ -41,13 +41,13 @@ class TaskHandler(object): """ to_save = False value = ServerConfig.objects.conf("delayed_tasks", default={}) - if isinstance(value, basestring): + if isinstance(value, str): tasks = dbunserialize(value) else: tasks = value # At this point, `tasks` contains a dictionary of still-serialized tasks - for task_id, value in tasks.items(): + for task_id, value in list(tasks.items()): date, callback, args, kwargs = dbunserialize(value) if isinstance(callback, tuple): # `callback` can be an object and name for instance methods @@ -64,7 +64,7 @@ class TaskHandler(object): def save(self): """Save the tasks in ServerConfig.""" - for task_id, (date, callback, args, kwargs) in self.tasks.items(): + for task_id, (date, callback, args, kwargs) in list(self.tasks.items()): if task_id in self.to_save: continue @@ -111,7 +111,7 @@ class TaskHandler(object): # Choose a free task_id safe_args = [] safe_kwargs = {} - used_ids = self.tasks.keys() + used_ids = list(self.tasks.keys()) task_id = 1 while task_id in used_ids: task_id += 1 @@ -127,7 +127,7 @@ class TaskHandler(object): else: safe_args.append(arg) - for key, value in kwargs.items(): + for key, value in list(kwargs.items()): try: dbserialize(value) except (TypeError, AttributeError): @@ -187,7 +187,7 @@ class TaskHandler(object): """ now = datetime.now() - for task_id, (date, callbac, args, kwargs) in self.tasks.items(): + for task_id, (date, callbac, args, kwargs) in list(self.tasks.items()): seconds = max(0, (date - now).total_seconds()) task.deferLater(reactor, seconds, self.do_task, task_id) diff --git a/evennia/scripts/tickerhandler.py b/evennia/scripts/tickerhandler.py index 36455329a0..5ca5384b76 100644 --- a/evennia/scripts/tickerhandler.py +++ b/evennia/scripts/tickerhandler.py @@ -113,7 +113,7 @@ class Ticker(object): self._to_add = [] self._to_remove = [] self._is_ticking = True - for store_key, (args, kwargs) in self.subscriptions.iteritems(): + for store_key, (args, kwargs) in self.subscriptions.items(): callback = yield kwargs.pop("_callback", "at_tick") obj = yield kwargs.pop("_obj", None) try: @@ -286,7 +286,7 @@ class TickerPool(object): if interval and interval in self.tickers: self.tickers[interval].stop() else: - for ticker in self.tickers.values(): + for ticker in list(self.tickers.values()): ticker.stop() @@ -332,10 +332,10 @@ class TickerHandler(object): outobj, outpath, outcallfunc = None, None, None if callable(callback): if inspect.ismethod(callback): - outobj = callback.im_self - outcallfunc = callback.im_func.func_name + outobj = callback.__self__ + outcallfunc = callback.__func__.__name__ elif inspect.isfunction(callback): - outpath = "%s.%s" % (callback.__module__, callback.func_name) + outpath = "%s.%s" % (callback.__module__, callback.__name__) outcallfunc = callback else: raise TypeError("%s is not a callable function or method." % callback) @@ -371,8 +371,8 @@ class TickerHandler(object): interval = int(interval) persistent = bool(persistent) packed_obj = pack_dbobj(obj) - methodname = callfunc if callfunc and isinstance(callfunc, basestring) else None - outpath = path if path and isinstance(path, basestring) else None + methodname = callfunc if callfunc and isinstance(callfunc, str) else None + outpath = path if path and isinstance(path, str) else None return (packed_obj, methodname, outpath, interval, idstring, persistent) def save(self): @@ -386,16 +386,16 @@ class TickerHandler(object): if self.ticker_storage: # get the current times so the tickers can be restarted with a delay later start_delays = dict((interval, ticker.task.next_call_time()) - for interval, ticker in self.ticker_pool.tickers.items()) + for interval, ticker in list(self.ticker_pool.tickers.items())) # remove any subscriptions that lost its object in the interim - to_save = {store_key: (args, kwargs) for store_key, (args, kwargs) in self.ticker_storage.items() + to_save = {store_key: (args, kwargs) for store_key, (args, kwargs) in list(self.ticker_storage.items()) if ((store_key[1] and ("_obj" in kwargs and kwargs["_obj"].pk) and hasattr(kwargs["_obj"], store_key[1])) or # a valid method with existing obj store_key[2])} # a path given # update the timers for the tickers - for store_key, (args, kwargs) in to_save.items(): + for store_key, (args, kwargs) in list(to_save.items()): interval = store_key[1] # this is a mutable, so it's updated in-place in ticker_storage kwargs["_start_delay"] = start_delays.get(interval, None) @@ -423,7 +423,7 @@ class TickerHandler(object): restored_tickers = dbunserialize(restored_tickers) self.ticker_storage = {} - for store_key, (args, kwargs) in restored_tickers.iteritems(): + for store_key, (args, kwargs) in restored_tickers.items(): try: # at this point obj is the actual object (or None) due to how # the dbunserialize works @@ -431,7 +431,7 @@ class TickerHandler(object): if not persistent and not server_reload: # this ticker will not be restarted continue - if isinstance(callfunc, basestring) and not obj: + if isinstance(callfunc, str) and not obj: # methods must have an existing object continue # we must rebuild the store_key here since obj must not be @@ -562,7 +562,7 @@ class TickerHandler(object): if interval is None: # return dict of all, ordered by interval return dict((interval, ticker.subscriptions) - for interval, ticker in self.ticker_pool.tickers.iteritems()) + for interval, ticker in self.ticker_pool.tickers.items()) else: # get individual interval ticker = self.ticker_pool.tickers.get(interval, None) @@ -579,8 +579,8 @@ class TickerHandler(object): """ store_keys = [] - for ticker in self.ticker_pool.tickers.itervalues(): - for (objtup, callfunc, path, interval, idstring, persistent), (args, kwargs) in ticker.subscriptions.iteritems(): + for ticker in self.ticker_pool.tickers.values(): + for (objtup, callfunc, path, interval, idstring, persistent), (args, kwargs) in ticker.subscriptions.items(): store_keys.append((kwargs.get("_obj", None), callfunc, path, interval, idstring, persistent)) return store_keys diff --git a/evennia/server/amp.py b/evennia/server/amp.py index 9694abd034..615f9b3799 100644 --- a/evennia/server/amp.py +++ b/evennia/server/amp.py @@ -15,16 +15,16 @@ Server - (AMP server) Handles all mud operations. The server holds its own list at startup and when a session connects/disconnects """ -from __future__ import print_function + # imports needed on both server and portal side import os import time from collections import defaultdict, namedtuple from itertools import count -from cStringIO import StringIO +from io import StringIO try: - import cPickle as pickle + import pickle as pickle except ImportError: import pickle from twisted.protocols import amp diff --git a/evennia/server/evennia_launcher.py b/evennia/server/evennia_launcher.py index 9ccf664410..559b9358bf 100644 --- a/evennia/server/evennia_launcher.py +++ b/evennia/server/evennia_launcher.py @@ -9,7 +9,7 @@ and portal through the evennia_runner. Run without arguments to get a menu. Run the script with the -h flag to see usage information. """ -from __future__ import print_function + from builtins import input, range import os @@ -535,7 +535,7 @@ def create_settings_file(init=True, secret_settings=False): if not init: # if not --init mode, settings file may already exist from before if os.path.exists(settings_path): - inp = input("%s already exists. Do you want to reset it? y/[N]> " % settings_path) + inp = eval(input("%s already exists. Do you want to reset it? y/[N]> " % settings_path)) if not inp.lower() == 'y': print ("Aborted.") return @@ -601,9 +601,9 @@ def check_database(): # Check so a database exists and is accessible from django.db import connection tables = connection.introspection.get_table_list(connection.cursor()) - if not tables or not isinstance(tables[0], basestring): # django 1.8+ + if not tables or not isinstance(tables[0], str): # django 1.8+ tables = [tableinfo.name for tableinfo in tables] - if tables and u'accounts_accountdb' in tables: + if tables and 'accounts_accountdb' in tables: # database exists and seems set up. Initialize evennia. evennia._init() # Try to get Account#1 @@ -632,7 +632,7 @@ def check_database(): res = "" while res.upper() != "Y": # ask for permission - res = input("Continue [Y]/N: ") + res = eval(input("Continue [Y]/N: ")) if res.upper() == "N": sys.exit() elif not res: @@ -993,9 +993,9 @@ def list_settings(keys): # a specific key table = evtable.EvTable(width=131) keys = [key.upper() for key in keys] - confs = dict((key, var) for key, var in evsettings.__dict__.items() + confs = dict((key, var) for key, var in list(evsettings.__dict__.items()) if key in keys) - for key, val in confs.items(): + for key, val in list(confs.items()): table.add_row(key, str(val)) print(table) @@ -1009,18 +1009,18 @@ def run_menu(): # menu loop print(MENU) - inp = input(" option > ") + inp = eval(input(" option > ")) # quitting and help if inp.lower() == 'q': return elif inp.lower() == 'h': print(HELP_ENTRY) - input("press to continue ...") + eval(input("press to continue ...")) continue elif inp.lower() in ('v', 'i', 'a'): print(show_version_info(about=True)) - input("press to continue ...") + eval(input("press to continue ...")) continue # options diff --git a/evennia/server/evennia_runner.py b/evennia/server/evennia_runner.py index 83e7bf4093..c17dde7045 100644 --- a/evennia/server/evennia_runner.py +++ b/evennia/server/evennia_runner.py @@ -14,13 +14,13 @@ upon returning, or not. A process returning != 0 will always stop, no matter the value of this file. """ -from __future__ import print_function + import os import sys from argparse import ArgumentParser from subprocess import Popen -import Queue -import thread +import queue +import _thread import evennia try: @@ -143,7 +143,7 @@ def start_services(server_argv, portal_argv, doexit=False): and then restarts them when they finish. """ global SERVER, PORTAL - processes = Queue.Queue() + processes = queue.Queue() def server_waiter(queue): try: @@ -167,7 +167,7 @@ def start_services(server_argv, portal_argv, doexit=False): try: if not doexit and get_restart_mode(PORTAL_RESTART) == "True": # start portal as interactive, reloadable thread - PORTAL = thread.start_new_thread(portal_waiter, (processes, )) + PORTAL = _thread.start_new_thread(portal_waiter, (processes, )) else: # normal operation: start portal as a daemon; # we don't care to monitor it for restart @@ -182,7 +182,7 @@ def start_services(server_argv, portal_argv, doexit=False): SERVER = Popen(server_argv, env=getenv()) else: # start server as a reloadable thread - SERVER = thread.start_new_thread(server_waiter, (processes, )) + SERVER = _thread.start_new_thread(server_waiter, (processes, )) except IOError as e: print(PROCESS_IOERROR.format(component="Server", traceback=e)) return @@ -207,14 +207,14 @@ def start_services(server_argv, portal_argv, doexit=False): if (message == "server_stopped" and int(rc) == 0 and get_restart_mode(SERVER_RESTART) in ("True", "reload", "reset")): print(PROCESS_RESTART.format(component="Server")) - SERVER = thread.start_new_thread(server_waiter, (processes, )) + SERVER = _thread.start_new_thread(server_waiter, (processes, )) continue # normally the portal is not reloaded since it's run as a daemon. if (message == "portal_stopped" and int(rc) == 0 and get_restart_mode(PORTAL_RESTART) == "True"): print(PROCESS_RESTART.format(component="Portal")) - PORTAL = thread.start_new_thread(portal_waiter, (processes, )) + PORTAL = _thread.start_new_thread(portal_waiter, (processes, )) continue break except ReactorNotRunning: diff --git a/evennia/server/initial_setup.py b/evennia/server/initial_setup.py index 985a54dc95..bae2cdf534 100644 --- a/evennia/server/initial_setup.py +++ b/evennia/server/initial_setup.py @@ -5,7 +5,7 @@ other things. Everything starts at handle_setup() """ -from __future__ import print_function + import time from django.conf import settings diff --git a/evennia/server/inputfuncs.py b/evennia/server/inputfuncs.py index 3715bb53fe..6d852767af 100644 --- a/evennia/server/inputfuncs.py +++ b/evennia/server/inputfuncs.py @@ -185,11 +185,11 @@ def client_options(session, *args, **kwargs): return {0: int(val)} def validate_bool(val): - if isinstance(val, basestring): + if isinstance(val, str): return True if val.lower() in ("true", "on", "1") else False return bool(val) - for key, value in kwargs.iteritems(): + for key, value in kwargs.items(): key = key.lower() if key == "client": flags["CLIENTNAME"] = to_str(value) @@ -254,7 +254,7 @@ def get_inputfuncs(session, *args, **kwargs): So we get it from the sessionhandler. """ inputfuncsdict = dict((key, func.__doc__) for key, func - in session.sessionhandler.get_inputfuncs().iteritems()) + in session.sessionhandler.get_inputfuncs().items()) session.msg(get_inputfuncs=inputfuncsdict) @@ -463,5 +463,5 @@ def webclient_options(session, *args, **kwargs): session=session) else: # kwargs provided: persist them to the account object - for key, value in kwargs.iteritems(): + for key, value in kwargs.items(): clientoptions[key] = value diff --git a/evennia/server/migrations/0001_initial.py b/evennia/server/migrations/0001_initial.py index bba61366b1..0ca07164ad 100644 --- a/evennia/server/migrations/0001_initial.py +++ b/evennia/server/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/evennia/server/models.py b/evennia/server/models.py index 7b4c2e9da8..92cfe88ed6 100644 --- a/evennia/server/models.py +++ b/evennia/server/models.py @@ -11,7 +11,7 @@ manager's conf() method. from builtins import object try: - import cPickle as pickle + import pickle as pickle except ImportError: import pickle diff --git a/evennia/server/portal/mssp.py b/evennia/server/portal/mssp.py index f80594c747..29a40ca285 100644 --- a/evennia/server/portal/mssp.py +++ b/evennia/server/portal/mssp.py @@ -191,7 +191,7 @@ class Mssp(object): self.mssp_table.update(MSSPTable_CUSTOM) varlist = '' - for variable, value in self.mssp_table.items(): + for variable, value in list(self.mssp_table.items()): if callable(value): value = value() if utils.is_iter(value): diff --git a/evennia/server/portal/portal.py b/evennia/server/portal/portal.py index 79002c2a0e..75cb77e837 100644 --- a/evennia/server/portal/portal.py +++ b/evennia/server/portal/portal.py @@ -7,7 +7,7 @@ sets up all the networking features. (this is done automatically by game/evennia.py). """ -from __future__ import print_function + from builtins import object import time diff --git a/evennia/server/portal/portalsessionhandler.py b/evennia/server/portal/portalsessionhandler.py index 171216f8d8..032fdfc5bb 100644 --- a/evennia/server/portal/portalsessionhandler.py +++ b/evennia/server/portal/portalsessionhandler.py @@ -1,8 +1,8 @@ """ Sessionhandler for portal sessions """ -from __future__ import print_function -from __future__ import division + + import time from collections import deque, namedtuple @@ -141,7 +141,7 @@ class PortalSessionHandler(SessionHandler): if self.portal.amp_protocol: # we only send sessdata that should not have changed # at the server level at this point - sessdata = dict((key, val) for key, val in sessdata.items() if key in ("protocol_key", + sessdata = dict((key, val) for key, val in list(sessdata.items()) if key in ("protocol_key", "address", "sessid", "csessid", @@ -188,7 +188,7 @@ class PortalSessionHandler(SessionHandler): # we set a watchdog to stop self.disconnect from deleting # sessions while we are looping over them. sessionhandler._disconnect_all = True - for session in sessionhandler.values(): + for session in list(sessionhandler.values()): session.disconnect() del sessionhandler._disconnect_all @@ -253,7 +253,7 @@ class PortalSessionHandler(SessionHandler): reason (str, optional): Motivation for disconnect. """ - for session in self.values(): + for session in list(self.values()): session.disconnect(reason) del session self.clear() @@ -336,7 +336,7 @@ class PortalSessionHandler(SessionHandler): send command. """ - for session in self.values(): + for session in list(self.values()): self.data_out(session, text=[[message], {}]) def data_in(self, session, **kwargs): @@ -412,7 +412,7 @@ class PortalSessionHandler(SessionHandler): # distribute outgoing data to the correct session methods. if session: - for cmdname, (cmdargs, cmdkwargs) in kwargs.iteritems(): + for cmdname, (cmdargs, cmdkwargs) in kwargs.items(): funcname = "send_%s" % cmdname.strip().lower() if hasattr(session, funcname): # better to use hassattr here over try..except diff --git a/evennia/server/portal/ssh.py b/evennia/server/portal/ssh.py index 7dde511637..980839b624 100644 --- a/evennia/server/portal/ssh.py +++ b/evennia/server/portal/ssh.py @@ -8,7 +8,7 @@ login procedure of the game, tracks sessions etc. Using standard ssh client, """ -from __future__ import print_function + from builtins import object import os import re diff --git a/evennia/server/portal/ssl.py b/evennia/server/portal/ssl.py index 209e08696f..761df6766d 100644 --- a/evennia/server/portal/ssl.py +++ b/evennia/server/portal/ssl.py @@ -3,7 +3,7 @@ This is a simple context factory for auto-creating SSL keys and certificates. """ -from __future__ import print_function + import os import sys diff --git a/evennia/server/portal/telnet_oob.py b/evennia/server/portal/telnet_oob.py index 50c8df7713..2d3060ca9c 100644 --- a/evennia/server/portal/telnet_oob.py +++ b/evennia/server/portal/telnet_oob.py @@ -208,7 +208,7 @@ class TelnetOOB(object): msdp_kwargs="".join("%s%s%s%s" % (MSDP_VAR, key, MSDP_VAL, json.dumps(val)) - for key, val in kwargs.iteritems())) + for key, val in kwargs.items())) msdp_string = msdp_args + msdp_kwargs @@ -316,7 +316,7 @@ class TelnetOOB(object): cmds = {} # merge matching table/array/variables together - for key, table in tables.iteritems(): + for key, table in tables.items(): args, kwargs = [], table if key in arrays: args.extend(arrays.pop(key)) @@ -324,13 +324,13 @@ class TelnetOOB(object): args.append(variables.pop(key)) cmds[key] = [args, kwargs] - for key, arr in arrays.iteritems(): + for key, arr in arrays.items(): args, kwargs = arr, {} if key in variables: args.append(variables.pop(key)) cmds[key] = [args, kwargs] - for key, var in variables.iteritems(): + for key, var in variables.items(): cmds[key] = [[var], {}] # print("msdp data in:", cmds) # DEBUG @@ -374,7 +374,7 @@ class TelnetOOB(object): args, kwargs = [], {} if hasattr(structure, "__iter__"): if isinstance(structure, dict): - kwargs = {key: value for key, value in structure.iteritems() if key} + kwargs = {key: value for key, value in structure.items() if key} else: args = list(structure) else: diff --git a/evennia/server/portal/webclient_ajax.py b/evennia/server/portal/webclient_ajax.py index c31f5d8eab..d780feabf7 100644 --- a/evennia/server/portal/webclient_ajax.py +++ b/evennia/server/portal/webclient_ajax.py @@ -87,7 +87,7 @@ class AjaxWebClient(resource.Resource): now = time.time() to_remove = [] keep_alives = ((csessid, remove) for csessid, (t, remove) - in self.last_alive.iteritems() if now - t > _KEEPALIVE) + in self.last_alive.items() if now - t > _KEEPALIVE) for csessid, remove in keep_alives: if remove: # keepalive timeout. Line is dead. diff --git a/evennia/server/profiling/dummyrunner.py b/evennia/server/profiling/dummyrunner.py index 34b6acafba..c008c4432b 100644 --- a/evennia/server/profiling/dummyrunner.py +++ b/evennia/server/profiling/dummyrunner.py @@ -30,8 +30,8 @@ in your settings. See utils.dummyrunner_actions.py for instructions on how to define this module. """ -from __future__ import print_function -from __future__ import division + + from builtins import range import sys diff --git a/evennia/server/profiling/memplot.py b/evennia/server/profiling/memplot.py index e8d7e76b12..5d031baaa3 100644 --- a/evennia/server/profiling/memplot.py +++ b/evennia/server/profiling/memplot.py @@ -6,7 +6,7 @@ the script will append to this file if it already exists. Call this module directly to plot the log (requires matplotlib and numpy). """ -from __future__ import division + import os import sys import time diff --git a/evennia/server/profiling/test_queries.py b/evennia/server/profiling/test_queries.py index 050dd9794a..6c0cf7a207 100644 --- a/evennia/server/profiling/test_queries.py +++ b/evennia/server/profiling/test_queries.py @@ -3,7 +3,7 @@ This is a little routine for viewing the sql queries that are executed by a give query as well as count them for optimization testing. """ -from __future__ import print_function + import sys import os #sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))) diff --git a/evennia/server/profiling/timetrace.py b/evennia/server/profiling/timetrace.py index 00a37c3f38..8f4ebb5791 100644 --- a/evennia/server/profiling/timetrace.py +++ b/evennia/server/profiling/timetrace.py @@ -1,7 +1,7 @@ """ Trace a message through the messaging system """ -from __future__ import print_function + import time diff --git a/evennia/server/server.py b/evennia/server/server.py index fb9f753b5c..a68f6d5463 100644 --- a/evennia/server/server.py +++ b/evennia/server/server.py @@ -7,7 +7,7 @@ sets up all the networking features. (this is done automatically by evennia/server/server_runner.py). """ -from __future__ import print_function + from builtins import object import time import sys @@ -131,7 +131,7 @@ def _server_maintenance(): # handle idle timeouts if _IDLE_TIMEOUT > 0: reason = _("idle timeout exceeded") - for session in (sess for sess in SESSIONS.values() + for session in (sess for sess in list(SESSIONS.values()) if (now - sess.cmd_last) > _IDLE_TIMEOUT): if not session.account or not \ session.account.access(session.account, "noidletimeout", default=False): @@ -237,8 +237,8 @@ class Evennia(object): "BASE_EXIT_TYPECLASS", "BASE_SCRIPT_TYPECLASS", "BASE_CHANNEL_TYPECLASS") # get previous and current settings so they can be compared - settings_compare = zip([ServerConfig.objects.conf(name) for name in settings_names], - [settings.__getattr__(name) for name in settings_names]) + settings_compare = list(zip([ServerConfig.objects.conf(name) for name in settings_names], + [settings.__getattr__(name) for name in settings_names])) mismatches = [i for i, tup in enumerate(settings_compare) if tup[0] and tup[1] and tup[0] != tup[1]] if len(mismatches): # can't use any() since mismatches may be [0] which reads as False for any() # we have a changed default. Import relevant objects and diff --git a/evennia/server/serversession.py b/evennia/server/serversession.py index aaaeaaaedd..acd59ad67f 100644 --- a/evennia/server/serversession.py +++ b/evennia/server/serversession.py @@ -141,7 +141,7 @@ class NAttributeHandler(object): """ if return_tuples: - return [(key, value) for (key, value) in self._store.items() if not key.startswith("_")] + return [(key, value) for (key, value) in list(self._store.items()) if not key.startswith("_")] return [key for key in self._store if not key.startswith("_")] @@ -459,7 +459,7 @@ class ServerSession(Session): def __unicode__(self): """Unicode representation""" - return u"%s" % str(self) + return "%s" % str(self) # Dummy API hooks for use during non-loggedin operation diff --git a/evennia/server/session.py b/evennia/server/session.py index 70be0708d7..496d15bfeb 100644 --- a/evennia/server/session.py +++ b/evennia/server/session.py @@ -105,7 +105,7 @@ class Session(object): the keys given by self._attrs_to_sync. """ - return dict((key, value) for key, value in self.__dict__.items() + return dict((key, value) for key, value in list(self.__dict__.items()) if key in self._attrs_to_sync) def load_sync_data(self, sessdata): @@ -117,7 +117,7 @@ class Session(object): sessdata (dict): Session data dictionary. """ - for propname, value in sessdata.items(): + for propname, value in list(sessdata.items()): setattr(self, propname, value) def at_sync(self): diff --git a/evennia/server/sessionhandler.py b/evennia/server/sessionhandler.py index 825cf6da30..734e6e0c27 100644 --- a/evennia/server/sessionhandler.py +++ b/evennia/server/sessionhandler.py @@ -26,7 +26,7 @@ from evennia.utils.utils import (variable_from_module, is_iter, from evennia.utils.inlinefuncs import parse_inlinefunc try: - import cPickle as pickle + import pickle as pickle except ImportError: import pickle @@ -145,7 +145,7 @@ class SessionHandler(dict): if include_unloggedin: return listvalues(self) else: - return [session for session in self.values() if session.logged_in] + return [session for session in list(self.values()) if session.logged_in] def get_all_sync_data(self): """ @@ -156,7 +156,7 @@ class SessionHandler(dict): syncdata (dict): A dict of sync data. """ - return dict((sessid, sess.get_sync_data()) for sessid, sess in self.items()) + return dict((sessid, sess.get_sync_data()) for sessid, sess in list(self.items())) def clean_senddata(self, session, kwargs): """ @@ -189,12 +189,12 @@ class SessionHandler(dict): "Helper function to convert data to AMP-safe (picketable) values" if isinstance(data, dict): newdict = {} - for key, part in data.items(): + for key, part in list(data.items()): newdict[key] = _validate(part) return newdict elif hasattr(data, "__iter__"): return [_validate(part) for part in data] - elif isinstance(data, basestring): + elif isinstance(data, str): # make sure strings are in a valid encoding try: data = data and to_str(to_unicode(data), encoding=session.protocol_flags["ENCODING"]) @@ -214,12 +214,12 @@ class SessionHandler(dict): elif hasattr(data, "id") and hasattr(data, "db_date_created") \ and hasattr(data, '__dbclass__'): # convert database-object to their string representation. - return _validate(unicode(data)) + return _validate(str(data)) else: return data rkwargs = {} - for key, data in kwargs.iteritems(): + for key, data in kwargs.items(): key = _validate(key) if not data: if key == "text": @@ -347,12 +347,12 @@ class ServerSessionHandler(SessionHandler): delayed_import() global _ServerSession, _AccountDB, _ServerConfig, _ScriptDB - for sess in self.values(): + for sess in list(self.values()): # we delete the old session to make sure to catch eventual # lingering references. del sess - for sessid, sessdict in portalsessionsdata.items(): + for sessid, sessdict in list(portalsessionsdata.items()): sess = _ServerSession() sess.sessionhandler = self sess.load_sync_data(sessdict) @@ -565,7 +565,7 @@ class ServerSessionHandler(SessionHandler): """ uid = curr_session.uid - doublet_sessions = [sess for sess in self.values() + doublet_sessions = [sess for sess in list(self.values()) if sess.logged_in and sess.uid == uid and sess != curr_session] @@ -580,7 +580,7 @@ class ServerSessionHandler(SessionHandler): """ tcurr = time.time() reason = _("Idle timeout exceeded, disconnecting.") - for session in (session for session in self.values() + for session in (session for session in list(self.values()) if session.logged_in and _IDLE_TIMEOUT > 0 and (tcurr - session.cmd_last) > _IDLE_TIMEOUT): self.disconnect(session, reason=reason) @@ -595,7 +595,7 @@ class ServerSessionHandler(SessionHandler): naccount (int): Number of connected accounts """ - return len(set(session.uid for session in self.values() if session.logged_in)) + return len(set(session.uid for session in list(self.values()) if session.logged_in)) def all_connected_accounts(self): """ @@ -606,7 +606,7 @@ class ServerSessionHandler(SessionHandler): amount of Sessions due to multi-playing). """ - return list(set(session.account for session in self.values() if session.logged_in and session.account)) + return list(set(session.account for session in list(self.values()) if session.logged_in and session.account)) def session_from_sessid(self, sessid): """ @@ -653,7 +653,7 @@ class ServerSessionHandler(SessionHandler): """ uid = account.uid - return [session for session in self.values() if session.logged_in and session.uid == uid] + return [session for session in list(self.values()) if session.logged_in and session.uid == uid] def sessions_from_puppet(self, puppet): """ @@ -680,7 +680,7 @@ class ServerSessionHandler(SessionHandler): csessid (str): The session hash """ - return [session for session in self.values() + return [session for session in list(self.values()) if session.csessid and session.csessid == csessid] def announce_all(self, message): @@ -691,7 +691,7 @@ class ServerSessionHandler(SessionHandler): message (str): Message to send. """ - for session in self.values(): + for session in list(self.values()): self.data_out(session, text=message) def data_out(self, session, **kwargs): @@ -754,7 +754,7 @@ class ServerSessionHandler(SessionHandler): # distribute incoming data to the correct receiving methods. if session: input_debug = session.protocol_flags.get("INPUTDEBUG", False) - for cmdname, (cmdargs, cmdkwargs) in kwargs.iteritems(): + for cmdname, (cmdargs, cmdkwargs) in kwargs.items(): cname = cmdname.strip().lower() try: cmdkwargs.pop("options", None) diff --git a/evennia/server/webserver.py b/evennia/server/webserver.py index 260b38071d..a06f537dac 100644 --- a/evennia/server/webserver.py +++ b/evennia/server/webserver.py @@ -11,8 +11,8 @@ application. a great example/aid on how to do this.) """ -import urlparse -from urllib import quote as urlquote +import urllib.parse +from urllib.parse import quote as urlquote from twisted.web import resource, http, server from twisted.internet import reactor from twisted.application import internet @@ -117,7 +117,7 @@ class EvenniaReverseProxyResource(ReverseProxyResource): # RFC 2616 tells us that we can omit the port if it's the default port, # but we have to provide it otherwise request.content.seek(0, 0) - qs = urlparse.urlparse(request.uri)[4] + qs = urllib.parse.urlparse(request.uri)[4] if qs: rest = self.path + '?' + qs else: diff --git a/evennia/settings_default.py b/evennia/settings_default.py index 779d2e3245..f7ce28e72c 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -259,8 +259,8 @@ CONN_MAX_AGE = 3600 * 7 # not be checked for models specified here. If new_natural_key does not exist, # `None` will be returned and stored back as if no replacement was set. ATTRIBUTE_STORED_MODEL_RENAME = [ - ((u"players", u"playerdb"), (u"accounts", u"accountdb")), - ((u"typeclasses", u"defaultplayer"), (u"typeclasses", u"defaultaccount"))] + (("players", "playerdb"), ("accounts", "accountdb")), + (("typeclasses", "defaultplayer"), ("typeclasses", "defaultaccount"))] ###################################################################### diff --git a/evennia/typeclasses/attributes.py b/evennia/typeclasses/attributes.py index 84a21c0c27..2fdbc5d6b7 100644 --- a/evennia/typeclasses/attributes.py +++ b/evennia/typeclasses/attributes.py @@ -168,7 +168,7 @@ class Attribute(SharedMemoryModel): return smart_str("%s(%s)" % (self.db_key, self.id)) def __unicode__(self): - return u"%s(%s)" % (self.db_key, self.id) + return "%s(%s)" % (self.db_key, self.id) def access(self, accessing_obj, access_type='read', default=False, **kwargs): """ @@ -299,7 +299,7 @@ class AttributeHandler(object): # for this category before catkey = "-%s" % category if _TYPECLASS_AGGRESSIVE_CACHE and catkey in self._catcache: - return [attr for key, attr in self._cache.items() if key.endswith(catkey) and attr] + return [attr for key, attr in list(self._cache.items()) if key.endswith(catkey) and attr] else: # we have to query to make this category up-date in the cache query = {"%s__id" % self._model: self._objid, @@ -350,7 +350,7 @@ class AttributeHandler(object): self._cache.pop(cachekey, None) else: self._cache = {key: attrobj for key, attrobj in - self._cache.items() if not key.endswith(catkey)} + list(self._cache.items()) if not key.endswith(catkey)} # mark that the category cache is no longer up-to-date self._catcache.pop(catkey, None) self._cache_complete = False @@ -655,10 +655,10 @@ class AttributeHandler(object): if not self._cache_complete: self._fullcache() if accessing_obj: - [attr.delete() for attr in self._cache.values() + [attr.delete() for attr in list(self._cache.values()) if attr and attr.access(accessing_obj, self._attredit, default=default_access)] else: - [attr.delete() for attr in self._cache.values() if attr and attr.pk] + [attr.delete() for attr in list(self._cache.values()) if attr and attr.pk] self._cache = {} self._catcache = {} self._cache_complete = False @@ -682,7 +682,7 @@ class AttributeHandler(object): """ if not self._cache_complete: self._fullcache() - attrs = sorted([attr for attr in self._cache.values() if attr], + attrs = sorted([attr for attr in list(self._cache.values()) if attr], key=lambda o: o.id) if accessing_obj: return [attr for attr in attrs @@ -903,7 +903,7 @@ class NickHandler(AttributeHandler): for category in make_iter(categories): nicks.update({nick.key: nick for nick in make_iter(self.obj.account.nicks.get( category=category, return_obj=True)) if nick and nick.key}) - for key, nick in nicks.iteritems(): + for key, nick in nicks.items(): nick_regex, template, _, _ = nick.value regex = self._regex_cache.get(nick_regex) if not regex: @@ -1001,5 +1001,5 @@ class NAttributeHandler(object): """ if return_tuples: - return [(key, value) for (key, value) in self._store.items() if not key.startswith("_")] + return [(key, value) for (key, value) in list(self._store.items()) if not key.startswith("_")] return [key for key in self._store if not key.startswith("_")] diff --git a/evennia/typeclasses/managers.py b/evennia/typeclasses/managers.py index 11a84f275a..47293eaa59 100644 --- a/evennia/typeclasses/managers.py +++ b/evennia/typeclasses/managers.py @@ -335,9 +335,9 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): either a string '#N' or an integer N. """ - if reqhash and not (isinstance(dbref, basestring) and dbref.startswith("#")): + if reqhash and not (isinstance(dbref, str) and dbref.startswith("#")): return None - if isinstance(dbref, basestring): + if isinstance(dbref, str): dbref = dbref.lstrip('#') try: if int(dbref) < 0: @@ -438,7 +438,7 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): if callable(typeclass): cls = typeclass.__class__ typeclass = "%s.%s" % (cls.__module__, cls.__name__) - elif not isinstance(typeclass, basestring) and hasattr(typeclass, "path"): + elif not isinstance(typeclass, str) and hasattr(typeclass, "path"): typeclass = typeclass.path # query objects of exact typeclass diff --git a/evennia/typeclasses/migrations/0001_initial.py b/evennia/typeclasses/migrations/0001_initial.py index 55b1daa7ca..214cc59803 100644 --- a/evennia/typeclasses/migrations/0001_initial.py +++ b/evennia/typeclasses/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations from django.conf import settings diff --git a/evennia/typeclasses/migrations/0002_auto_20150109_0913.py b/evennia/typeclasses/migrations/0002_auto_20150109_0913.py index 66753f4830..75b5e7f558 100644 --- a/evennia/typeclasses/migrations/0002_auto_20150109_0913.py +++ b/evennia/typeclasses/migrations/0002_auto_20150109_0913.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/evennia/typeclasses/migrations/0003_defaultcharacter_defaultexit_defaultguest_defaultobject_defaultplayer_defaultroom_defaultscript_dono.py b/evennia/typeclasses/migrations/0003_defaultcharacter_defaultexit_defaultguest_defaultobject_defaultplayer_defaultroom_defaultscript_dono.py index 8b966bfb86..d308822f70 100644 --- a/evennia/typeclasses/migrations/0003_defaultcharacter_defaultexit_defaultguest_defaultobject_defaultplayer_defaultroom_defaultscript_dono.py +++ b/evennia/typeclasses/migrations/0003_defaultcharacter_defaultexit_defaultguest_defaultobject_defaultplayer_defaultroom_defaultscript_dono.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations import evennia.accounts.manager diff --git a/evennia/typeclasses/migrations/0004_auto_20151101_1759.py b/evennia/typeclasses/migrations/0004_auto_20151101_1759.py index 042462318a..a14017f6e6 100644 --- a/evennia/typeclasses/migrations/0004_auto_20151101_1759.py +++ b/evennia/typeclasses/migrations/0004_auto_20151101_1759.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import migrations, models diff --git a/evennia/typeclasses/migrations/0005_auto_20160625_1812.py b/evennia/typeclasses/migrations/0005_auto_20160625_1812.py index b3fd24f568..98997d89d1 100644 --- a/evennia/typeclasses/migrations/0005_auto_20160625_1812.py +++ b/evennia/typeclasses/migrations/0005_auto_20160625_1812.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-06-25 18:12 -from __future__ import unicode_literals + from django.db import migrations diff --git a/evennia/typeclasses/migrations/0006_auto_add_dbmodel_value_for_tags_attributes.py b/evennia/typeclasses/migrations/0006_auto_add_dbmodel_value_for_tags_attributes.py index f2cb9c23cc..33cb0a8640 100644 --- a/evennia/typeclasses/migrations/0006_auto_add_dbmodel_value_for_tags_attributes.py +++ b/evennia/typeclasses/migrations/0006_auto_add_dbmodel_value_for_tags_attributes.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.11 on 2017-01-21 22:30 -from __future__ import unicode_literals + from django.db import migrations diff --git a/evennia/typeclasses/migrations/0007_tag_migrations_may_be_slow.py b/evennia/typeclasses/migrations/0007_tag_migrations_may_be_slow.py index 443ade55c6..db15e5b54f 100644 --- a/evennia/typeclasses/migrations/0007_tag_migrations_may_be_slow.py +++ b/evennia/typeclasses/migrations/0007_tag_migrations_may_be_slow.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.11 on 2017-01-25 22:30 -from __future__ import unicode_literals + from django.db import migrations diff --git a/evennia/typeclasses/migrations/0008_lock_and_perm_rename.py b/evennia/typeclasses/migrations/0008_lock_and_perm_rename.py index 670a7c8b08..1044c04226 100644 --- a/evennia/typeclasses/migrations/0008_lock_and_perm_rename.py +++ b/evennia/typeclasses/migrations/0008_lock_and_perm_rename.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.11 on 2017-01-25 22:30 -from __future__ import unicode_literals + import re from django.db import migrations diff --git a/evennia/typeclasses/migrations/0009_rename_player_cmdsets_typeclasses.py b/evennia/typeclasses/migrations/0009_rename_player_cmdsets_typeclasses.py index dfbfdcbf6f..84523de9a5 100644 --- a/evennia/typeclasses/migrations/0009_rename_player_cmdsets_typeclasses.py +++ b/evennia/typeclasses/migrations/0009_rename_player_cmdsets_typeclasses.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.3 on 2017-07-09 21:33 -from __future__ import unicode_literals + import re from django.db import migrations diff --git a/evennia/typeclasses/migrations/0010_delete_old_player_tables.py b/evennia/typeclasses/migrations/0010_delete_old_player_tables.py index 30abe534b1..0c9e37364f 100644 --- a/evennia/typeclasses/migrations/0010_delete_old_player_tables.py +++ b/evennia/typeclasses/migrations/0010_delete_old_player_tables.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by Django 1.11.3 on 2017-07-13 18:47 -from __future__ import unicode_literals + from django.db import migrations, connection diff --git a/evennia/typeclasses/models.py b/evennia/typeclasses/models.py index c156aa2ac6..dbe48d7705 100644 --- a/evennia/typeclasses/models.py +++ b/evennia/typeclasses/models.py @@ -340,7 +340,7 @@ class TypedObject(SharedMemoryModel): return smart_str("%s" % self.db_key) def __unicode__(self): - return u"%s" % self.db_key + return "%s" % self.db_key #@property def __dbid_get(self): @@ -430,7 +430,7 @@ class TypedObject(SharedMemoryModel): typeclass. """ - if isinstance(typeclass, basestring): + if isinstance(typeclass, str): typeclass = [typeclass] + ["%s.%s" % (prefix, typeclass) for prefix in settings.TYPECLASS_PATHS] else: typeclass = [typeclass.path] diff --git a/evennia/typeclasses/tags.py b/evennia/typeclasses/tags.py index e7c3bfdbb1..e92c0ece42 100644 --- a/evennia/typeclasses/tags.py +++ b/evennia/typeclasses/tags.py @@ -67,7 +67,7 @@ class Tag(models.Model): index_together = (('db_key', 'db_category', 'db_tagtype', 'db_model'),) def __unicode__(self): - return u"" % (self.db_key, "(category:%s)" % self.db_category if self.db_category else "") + return "" % (self.db_key, "(category:%s)" % self.db_category if self.db_category else "") def __str__(self): return str("" % (self.db_key, "(category:%s)" % self.db_category if self.db_category else "")) @@ -166,7 +166,7 @@ class TagHandler(object): # for this category before catkey = "-%s" % category if _TYPECLASS_AGGRESSIVE_CACHE and catkey in self._catcache: - return [tag for key, tag in self._cache.items() if key.endswith(catkey)] + return [tag for key, tag in list(self._cache.items()) if key.endswith(catkey)] else: # we have to query to make this category up-date in the cache query = {"%s__id" % self._model: self._objid, @@ -394,14 +394,14 @@ class TagHandler(object): else: keys[tup[1]].append(tup[0]) data[tup[1]] = tup[2] # overwrite previous - for category, key in keys.iteritems(): + for category, key in keys.items(): self.add(tag=key, category=category, data=data.get(category, None)) def __str__(self): return ",".join(self.all()) def __unicode(self): - return u",".join(self.all()) + return ",".join(self.all()) class AliasHandler(TagHandler): diff --git a/evennia/utils/__init__.py b/evennia/utils/__init__.py index 2672699a7e..a34c39704c 100644 --- a/evennia/utils/__init__.py +++ b/evennia/utils/__init__.py @@ -4,7 +4,7 @@ modules in Evennia. It also holds the idmapper in-memory caching functionality. """ -from __future__ import absolute_import + # simple check to determine if we are currently running under pypy. try: import __pypy__ as is_pypy diff --git a/evennia/utils/ansi.py b/evennia/utils/ansi.py index f4e63cce85..a1a3ebf0e1 100644 --- a/evennia/utils/ansi.py +++ b/evennia/utils/ansi.py @@ -538,7 +538,7 @@ def _spacing_preflight(func): def wrapped(self, width, fillchar=None): if fillchar is None: fillchar = " " - if (len(fillchar) != 1) or (not isinstance(fillchar, basestring)): + if (len(fillchar) != 1) or (not isinstance(fillchar, str)): raise TypeError("must be char, not %s" % type(fillchar)) if not isinstance(width, int): raise TypeError("integer argument expected, got %s" % type(width)) @@ -579,7 +579,7 @@ def _on_raw(func_name): # just skip out if there are no more strings pass result = getattr(self._raw_string, func_name)(*args, **kwargs) - if isinstance(result, basestring): + if isinstance(result, str): return ANSIString(result, decoded=True) return result return wrapped @@ -633,7 +633,7 @@ class ANSIMeta(type): super(ANSIMeta, cls).__init__(*args, **kwargs) -class ANSIString(with_metaclass(ANSIMeta, unicode)): +class ANSIString(with_metaclass(ANSIMeta, str)): """ Unicode-like object that is aware of ANSI codes. @@ -674,7 +674,7 @@ class ANSIString(with_metaclass(ANSIMeta, unicode)): """ string = args[0] - if not isinstance(string, basestring): + if not isinstance(string, str): string = to_str(string, force_string=True) parser = kwargs.get('parser', ANSI_PARSER) decoded = kwargs.get('decoded', False) or hasattr(string, '_raw_string') @@ -705,7 +705,7 @@ class ANSIString(with_metaclass(ANSIMeta, unicode)): # It's a string that has been pre-ansi decoded. clean_string = parser.strip_raw_codes(string) - if not isinstance(string, unicode): + if not isinstance(string, str): string = string.decode('utf-8') ansi_string = super(ANSIString, cls).__new__(ANSIString, to_str(clean_string), "utf-8") @@ -803,7 +803,7 @@ class ANSIString(with_metaclass(ANSIMeta, unicode)): interpreted literally. """ - if not isinstance(other, basestring): + if not isinstance(other, str): return NotImplemented if not isinstance(other, ANSIString): other = ANSIString(other) @@ -814,7 +814,7 @@ class ANSIString(with_metaclass(ANSIMeta, unicode)): Likewise, if we're on the other end. """ - if not isinstance(other, basestring): + if not isinstance(other, str): return NotImplemented if not isinstance(other, ANSIString): other = ANSIString(other) @@ -976,7 +976,7 @@ class ANSIString(with_metaclass(ANSIMeta, unicode)): code_indexes = [] for match in self.parser.ansi_regex.finditer(self._raw_string): - code_indexes.extend(range(match.start(), match.end())) + code_indexes.extend(list(range(match.start(), match.end()))) if not code_indexes: # Plain string, no ANSI codes. return code_indexes, list(range(0, len(self._raw_string))) @@ -1285,7 +1285,7 @@ class ANSIString(with_metaclass(ANSIMeta, unicode)): code_indexes = [i for i in range(0, len(prefix))] length = len(prefix) + len(line) code_indexes.extend([i for i in range(length, length + len(postfix))]) - char_indexes = self._shifter(range(0, len(line)), len(prefix)) + char_indexes = self._shifter(list(range(0, len(line))), len(prefix)) raw_string = prefix + line + postfix return ANSIString( raw_string, clean_string=line, char_indexes=char_indexes, diff --git a/evennia/utils/batchprocessors.py b/evennia/utils/batchprocessors.py index a35e6d37f8..174a296943 100644 --- a/evennia/utils/batchprocessors.py +++ b/evennia/utils/batchprocessors.py @@ -382,7 +382,7 @@ class BatchCodeProcessor(object): """ # define the execution environment environdict = {"settings_module": settings, "DEBUG": debug} - for key, value in extra_environ.items(): + for key, value in list(extra_environ.items()): environdict[key] = value # initializing the django settings at the top of code diff --git a/evennia/utils/create.py b/evennia/utils/create.py index 1404e4caaa..8829bb922e 100644 --- a/evennia/utils/create.py +++ b/evennia/utils/create.py @@ -97,7 +97,7 @@ def create_object(typeclass=None, key=None, location=None, home=None, tags = make_iter(tags) if tags is not None else None - if isinstance(typeclass, basestring): + if isinstance(typeclass, str): # a path is given. Load the actual typeclass typeclass = class_from_module(typeclass, settings.TYPECLASS_PATHS) @@ -181,7 +181,7 @@ def create_script(typeclass=None, key=None, obj=None, account=None, locks=None, typeclass = typeclass if typeclass else settings.BASE_SCRIPT_TYPECLASS - if isinstance(typeclass, basestring): + if isinstance(typeclass, str): # a path is given. Load the actual typeclass typeclass = class_from_module(typeclass, settings.TYPECLASS_PATHS) @@ -353,7 +353,7 @@ def create_channel(key, aliases=None, desc=None, """ typeclass = typeclass if typeclass else settings.BASE_CHANNEL_TYPECLASS - if isinstance(typeclass, basestring): + if isinstance(typeclass, str): # a path is given. Load the actual typeclass typeclass = class_from_module(typeclass, settings.TYPECLASS_PATHS) @@ -418,7 +418,7 @@ def create_account(key, email, password, locks = make_iter(locks) if locks is not None else None permissions = make_iter(permissions) if permissions is not None else None - if isinstance(typeclass, basestring): + if isinstance(typeclass, str): # a path is given. Load the actual typeclass. typeclass = class_from_module(typeclass, settings.TYPECLASS_PATHS) diff --git a/evennia/utils/dbserialize.py b/evennia/utils/dbserialize.py index 80203923e8..dfc184d064 100644 --- a/evennia/utils/dbserialize.py +++ b/evennia/utils/dbserialize.py @@ -24,7 +24,7 @@ from functools import update_wrapper from collections import defaultdict, MutableSequence, MutableSet, MutableMapping from collections import OrderedDict, deque try: - from cPickle import dumps, loads + from pickle import dumps, loads except ImportError: from pickle import dumps, loads from django.core.exceptions import ObjectDoesNotExist @@ -157,7 +157,7 @@ class _SaverMutable(object): self._db_obj = kwargs.pop("_db_obj", None) self._data = None - def __nonzero__(self): + def __bool__(self): """Make sure to evaluate as False if empty""" return bool(self._data) @@ -183,7 +183,7 @@ class _SaverMutable(object): def process_tree(item, parent): """recursively populate the tree, storing parents""" dtype = type(item) - if dtype in (basestring, int, float, bool, tuple): + if dtype in (str, int, float, bool, tuple): return item elif dtype == list: dat = _SaverList(_parent=parent) @@ -191,7 +191,7 @@ class _SaverMutable(object): return dat elif dtype == dict: dat = _SaverDict(_parent=parent) - dat._data.update((key, process_tree(val, dat)) for key, val in item.items()) + dat._data.update((key, process_tree(val, dat)) for key, val in list(item.items())) return dat elif dtype == set: dat = _SaverSet(_parent=parent) @@ -493,18 +493,18 @@ def to_pickle(data): def process_item(item): """Recursive processor and identification of data""" dtype = type(item) - if dtype in (basestring, int, float, bool): + if dtype in (str, int, float, bool): return item elif dtype == tuple: return tuple(process_item(val) for val in item) elif dtype in (list, _SaverList): return [process_item(val) for val in item] elif dtype in (dict, _SaverDict): - return dict((process_item(key), process_item(val)) for key, val in item.items()) + return dict((process_item(key), process_item(val)) for key, val in list(item.items())) elif dtype in (set, _SaverSet): return set(process_item(val) for val in item) elif dtype in (OrderedDict, _SaverOrderedDict): - return OrderedDict((process_item(key), process_item(val)) for key, val in item.items()) + return OrderedDict((process_item(key), process_item(val)) for key, val in list(item.items())) elif dtype in (deque, _SaverDeque): return deque(process_item(val) for val in item) @@ -545,7 +545,7 @@ def from_pickle(data, db_obj=None): def process_item(item): """Recursive processor and identification of data""" dtype = type(item) - if dtype in (basestring, int, float, bool): + if dtype in (str, int, float, bool): return item elif _IS_PACKED_DBOBJ(item): # this must be checked before tuple @@ -555,11 +555,11 @@ def from_pickle(data, db_obj=None): elif dtype == tuple: return tuple(process_item(val) for val in item) elif dtype == dict: - return dict((process_item(key), process_item(val)) for key, val in item.items()) + return dict((process_item(key), process_item(val)) for key, val in list(item.items())) elif dtype == set: return set(process_item(val) for val in item) elif dtype == OrderedDict: - return OrderedDict((process_item(key), process_item(val)) for key, val in item.items()) + return OrderedDict((process_item(key), process_item(val)) for key, val in list(item.items())) elif dtype == deque: return deque(process_item(val) for val in item) elif hasattr(item, '__iter__'): @@ -574,7 +574,7 @@ def from_pickle(data, db_obj=None): def process_tree(item, parent): """Recursive processor, building a parent-tree from iterable data""" dtype = type(item) - if dtype in (basestring, int, float, bool): + if dtype in (str, int, float, bool): return item elif _IS_PACKED_DBOBJ(item): # this must be checked before tuple @@ -588,7 +588,7 @@ def from_pickle(data, db_obj=None): elif dtype == dict: dat = _SaverDict(_parent=parent) dat._data.update((process_item(key), process_tree(val, dat)) - for key, val in item.items()) + for key, val in list(item.items())) return dat elif dtype == set: dat = _SaverSet(_parent=parent) @@ -597,7 +597,7 @@ def from_pickle(data, db_obj=None): elif dtype == OrderedDict: dat = _SaverOrderedDict(_parent=parent) dat._data.update((process_item(key), process_tree(val, dat)) - for key, val in item.items()) + for key, val in list(item.items())) return dat elif dtype == deque: dat = _SaverDeque(_parent=parent) @@ -625,7 +625,7 @@ def from_pickle(data, db_obj=None): elif dtype == dict: dat = _SaverDict(_db_obj=db_obj) dat._data.update((process_item(key), process_tree(val, dat)) - for key, val in data.items()) + for key, val in list(data.items())) return dat elif dtype == set: dat = _SaverSet(_db_obj=db_obj) @@ -634,7 +634,7 @@ def from_pickle(data, db_obj=None): elif dtype == OrderedDict: dat = _SaverOrderedDict(_db_obj=db_obj) dat._data.update((process_item(key), process_tree(val, dat)) - for key, val in data.items()) + for key, val in list(data.items())) return dat elif dtype == deque: dat = _SaverDeque(_db_obj=db_obj) diff --git a/evennia/utils/eveditor.py b/evennia/utils/eveditor.py index 2ec3865eeb..990c826b33 100644 --- a/evennia/utils/eveditor.py +++ b/evennia/utils/eveditor.py @@ -336,7 +336,7 @@ def _load_editor(caller): setattr(eveditor, "_undo_pos", len(saved_undo) - 1) setattr(eveditor, "_unsaved", unsaved) setattr(eveditor, "_indent", indent) - for key, value in saved_options[1].iteritems(): + for key, value in saved_options[1].items(): setattr(eveditor, key, value) else: # something went wrong. Cleanup. @@ -800,7 +800,7 @@ class EvEditor(object): """ try: self._buffer = self._loadfunc(self._caller) - if not isinstance(self._buffer, basestring): + if not isinstance(self._buffer, str): self._buffer = to_str(self._buffer, force_string=True) self._caller.msg("|rNote: input buffer was converted to a string.|n") except Exception as e: @@ -965,7 +965,7 @@ class EvEditor(object): # If the line begins by one of the given keywords indent = self._indent - if any(line.startswith(kw) for kw in keywords.keys()): + if any(line.startswith(kw) for kw in list(keywords.keys())): # Get the keyword and matching begin tags keyword = [kw for kw in keywords if line.startswith(kw)][0] begin_tags = keywords[keyword] diff --git a/evennia/utils/evform.py b/evennia/utils/evform.py index 55fa0ec9e2..44d9f86cf4 100644 --- a/evennia/utils/evform.py +++ b/evennia/utils/evform.py @@ -134,7 +134,7 @@ into (when including its borders and at least one line of text), the form will raise an error. """ -from __future__ import print_function + from builtins import object, range import re @@ -155,12 +155,12 @@ _ANSI_ESCAPE = re.compile(r"\|\|") def _to_ansi(obj, regexable=False): "convert to ANSIString" - if isinstance(obj, basestring): + if isinstance(obj, str): # since ansi will be parsed twice (here and in the normal ansi send), we have to # escape the |-structure twice. obj = _ANSI_ESCAPE.sub(r"||||", obj) if isinstance(obj, dict): - return dict((key, _to_ansi(value, regexable=regexable)) for key, value in obj.items()) + return dict((key, _to_ansi(value, regexable=regexable)) for key, value in list(obj.items())) elif hasattr(obj, "__iter__"): return [_to_ansi(o) for o in obj] else: @@ -196,8 +196,8 @@ class EvForm(object): self.filename = filename self.input_form_dict = form - self.cells_mapping = dict((to_str(key, force_string=True), value) for key, value in cells.items()) if cells else {} - self.tables_mapping = dict((to_str(key, force_string=True), value) for key, value in tables.items()) if tables else {} + self.cells_mapping = dict((to_str(key, force_string=True), value) for key, value in list(cells.items())) if cells else {} + self.tables_mapping = dict((to_str(key, force_string=True), value) for key, value in list(tables.items())) if tables else {} self.cellchar = "x" self.tablechar = "c" @@ -259,7 +259,7 @@ class EvForm(object): break # get rectangles and assign EvCells - for key, (iy, leftix, rightix) in cell_coords.items(): + for key, (iy, leftix, rightix) in list(cell_coords.items()): # scan up to find top of rectangle dy_up = 0 @@ -295,7 +295,7 @@ class EvForm(object): mapping[key] = (iyup, leftix, width, height, EvCell(data, width=width, height=height, **options)) # get rectangles and assign Tables - for key, (iy, leftix, rightix) in table_coords.items(): + for key, (iy, leftix, rightix) in list(table_coords.items()): # scan up to find top of rectangle dy_up = 0 @@ -341,7 +341,7 @@ class EvForm(object): """ form = copy.copy(raw_form) - for key, (iy0, ix0, width, height, cell_or_table) in mapping.items(): + for key, (iy0, ix0, width, height, cell_or_table) in list(mapping.items()): # rect is a list of lines, each wide rect = cell_or_table.get() for il, rectline in enumerate(rect): @@ -368,8 +368,8 @@ class EvForm(object): kwargs.pop("width", None) kwargs.pop("height", None) - new_cells = dict((to_str(key, force_string=True), value) for key, value in cells.items()) if cells else {} - new_tables = dict((to_str(key, force_string=True), value) for key, value in tables.items()) if tables else {} + new_cells = dict((to_str(key, force_string=True), value) for key, value in list(cells.items())) if cells else {} + new_tables = dict((to_str(key, force_string=True), value) for key, value in list(tables.items())) if tables else {} self.cells_mapping.update(new_cells) self.tables_mapping.update(new_tables) @@ -424,7 +424,7 @@ class EvForm(object): def __unicode__(self): "prints the form" - return unicode(ANSIString("\n").join([line for line in self.form])) + return str(ANSIString("\n").join([line for line in self.form])) def _test(): @@ -453,4 +453,4 @@ def _test(): form.map(tables={"A": tableA, "B": tableB}) # unicode is required since the example contains non-ascii characters - return unicode(form) + return str(form) diff --git a/evennia/utils/evmenu.py b/evennia/utils/evmenu.py index a595366f5c..d3cd7ddb64 100644 --- a/evennia/utils/evmenu.py +++ b/evennia/utils/evmenu.py @@ -156,7 +156,7 @@ your default cmdset. Run it with this module, like `testmenu evennia.utils.evmenu`. """ -from __future__ import print_function + import random from builtins import object, range @@ -441,7 +441,7 @@ class EvMenu(object): "cmd_on_exit", "default", "nodetext", "helptext", "options", "cmdset_mergetype", "auto_quit")).intersection(set(kwargs.keys())): raise RuntimeError("One or more of the EvMenu `**kwargs` is reserved by EvMenu for internal use.") - for key, val in kwargs.iteritems(): + for key, val in kwargs.items(): setattr(self, key, val) # @@ -511,7 +511,7 @@ class EvMenu(object): else: # a python path of a module module = mod_import(menudata) - return dict((key, func) for key, func in module.__dict__.items() + return dict((key, func) for key, func in list(module.__dict__.items()) if isfunction(func) and not key.startswith("_")) def _format_node(self, nodetext, optionlist): @@ -663,7 +663,7 @@ class EvMenu(object): logger.log_trace(errmsg) return - if isinstance(ret, basestring): + if isinstance(ret, str): # only return a value if a string (a goto target), ignore all other returns return ret, kwargs return None @@ -934,7 +934,7 @@ class EvMenu(object): table[icol] = [pad(part, width=col_width + colsep, align="l") for part in table[icol]] # format the table into columns - return unicode(EvTable(table=table, border="none")) + return str(EvTable(table=table, border="none")) def node_formatter(self, nodetext, optionstext): """ diff --git a/evennia/utils/evtable.py b/evennia/utils/evtable.py index 31218189ab..b791b9911f 100644 --- a/evennia/utils/evtable.py +++ b/evennia/utils/evtable.py @@ -114,7 +114,7 @@ you need to re-set the color to have it appear on both sides of the table string. """ -from __future__ import print_function + from builtins import object, range from future.utils import listitems @@ -141,7 +141,7 @@ def _to_ansi(obj): return ANSIString(to_unicode(obj)) -_unicode = unicode +_unicode = str _whitespace = '\t\n\x0b\x0c\r ' @@ -810,7 +810,7 @@ class EvCell(object): self.trim_vertical = kwargs.pop("trim_vertical", self.trim_vertical) # fill all other properties - for key, value in kwargs.items(): + for key, value in list(kwargs.items()): setattr(self, key, value) # Handle sizes @@ -841,17 +841,17 @@ class EvCell(object): def __repr__(self): self.formatted = self._reformat() - return unicode(ANSIString("" % self.formatted)) + return str(ANSIString("" % self.formatted)) def __str__(self): "returns cell contents on string form" self.formatted = self._reformat() - return str(unicode(ANSIString("\n").join(self.formatted))) + return str(str(ANSIString("\n").join(self.formatted))) def __unicode__(self): "returns cell contents" self.formatted = self._reformat() - return unicode(ANSIString("\n").join(self.formatted)) + return str(ANSIString("\n").join(self.formatted)) # EvColumn class @@ -1424,7 +1424,7 @@ class EvTable(object): header = kwargs.get("header", None) if header: - column.add_rows(unicode(header), ypos=0, **options) + column.add_rows(str(header), ypos=0, **options) self.header = True elif self.header: # we have a header already. Offset @@ -1519,7 +1519,7 @@ class EvTable(object): """ self.width = kwargs.pop("width", self.width) self.height = kwargs.pop("height", self.height) - for key, value in kwargs.items(): + for key, value in list(kwargs.items()): setattr(self, key, value) hchar = kwargs.pop("header_line_char", self.header_line_char) @@ -1569,10 +1569,10 @@ class EvTable(object): def __str__(self): """print table (this also balances it)""" - return str(unicode(ANSIString("\n").join([line for line in self._generate_lines()]))) + return str(str(ANSIString("\n").join([line for line in self._generate_lines()]))) def __unicode__(self): - return unicode(ANSIString("\n").join([line for line in self._generate_lines()])) + return str(ANSIString("\n").join([line for line in self._generate_lines()])) def _test(): @@ -1580,11 +1580,11 @@ def _test(): table = EvTable("|yHeading1|n", "|gHeading2|n", table=[[1, 2, 3], [4, 5, 6], [7, 8, 9]], border="cells", align="l") table.add_column("|rThis is long data|n", "|bThis is even longer data|n") table.add_row("This is a single row") - print(unicode(table)) + print(str(table)) table.reformat(width=50) - print(unicode(table)) + print(str(table)) table.reformat_column(3, width=30, align='r') - print(unicode(table)) + print(str(table)) return table diff --git a/evennia/utils/gametime.py b/evennia/utils/gametime.py index 793e348143..a6d21907c9 100644 --- a/evennia/utils/gametime.py +++ b/evennia/utils/gametime.py @@ -5,7 +5,7 @@ It also supplies some useful methods to convert between in-mud time and real-world time as well allows to get the total runtime of the server and the current uptime. """ -from __future__ import division + import time from calendar import monthrange from datetime import datetime, timedelta diff --git a/evennia/utils/idmapper/models.py b/evennia/utils/idmapper/models.py index fcda53cdfd..326c387f55 100644 --- a/evennia/utils/idmapper/models.py +++ b/evennia/utils/idmapper/models.py @@ -6,7 +6,7 @@ leave caching unexpectedly (no use of WeakRefs). Also adds `cache_size()` for monitoring the size of the cache. """ -from __future__ import absolute_import, division + from builtins import object from future.utils import listitems, listvalues, with_metaclass @@ -141,7 +141,7 @@ class SharedMemoryModelBase(ModelBase): "Setter only used on foreign key relations, allows setting with #dbref" if _GA(cls, "_is_deleted"): raise ObjectDoesNotExist("Cannot set %s to %s: Hosting object was already deleted!" % (fname, value)) - if isinstance(value, (basestring, int)): + if isinstance(value, (str, int)): value = to_str(value, force_string=True) if (value.isdigit() or value.startswith("#")): # we also allow setting using dbrefs, if so we try to load the matching object. @@ -328,7 +328,7 @@ class SharedMemoryModel(with_metaclass(SharedMemoryModelBase, Model)): if force: cls.__dbclass__.__instance_cache__ = {} else: - cls.__dbclass__.__instance_cache__ = dict((key, obj) for key, obj in cls.__dbclass__.__instance_cache__.items() + cls.__dbclass__.__instance_cache__ = dict((key, obj) for key, obj in list(cls.__dbclass__.__instance_cache__.items()) if not obj.at_idmapper_flush()) #flush_instance_cache = classmethod(flush_instance_cache) diff --git a/evennia/utils/idmapper/tests.py b/evennia/utils/idmapper/tests.py index 4647b4a824..374eb0187a 100644 --- a/evennia/utils/idmapper/tests.py +++ b/evennia/utils/idmapper/tests.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import + from builtins import range from django.test import TestCase @@ -44,21 +44,21 @@ class SharedMemorysTest(TestCase): article_list = Article.objects.all().select_related('category') last_article = article_list[0] for article in article_list[1:]: - self.assertEquals(article.category is last_article.category, True) + self.assertEqual(article.category is last_article.category, True) last_article = article def testRegularReferences(self): article_list = RegularArticle.objects.all().select_related('category') last_article = article_list[0] for article in article_list[1:]: - self.assertEquals(article.category2 is last_article.category2, False) + self.assertEqual(article.category2 is last_article.category2, False) last_article = article def testMixedReferences(self): article_list = RegularArticle.objects.all().select_related('category') last_article = article_list[0] for article in article_list[1:]: - self.assertEquals(article.category is last_article.category, True) + self.assertEqual(article.category is last_article.category, True) last_article = article #article_list = Article.objects.all().select_related('category') @@ -74,4 +74,4 @@ class SharedMemorysTest(TestCase): article = Article.objects.all()[0:1].get() pk = article.pk article.delete() - self.assertEquals(pk not in Article.__instance_cache__, True) + self.assertEqual(pk not in Article.__instance_cache__, True) diff --git a/evennia/utils/inlinefuncs.py b/evennia/utils/inlinefuncs.py index e103e217d7..118f4fea5d 100644 --- a/evennia/utils/inlinefuncs.py +++ b/evennia/utils/inlinefuncs.py @@ -1,464 +1,464 @@ -""" -Inline functions (nested form). - -This parser accepts nested inlinefunctions on the form - -``` -$funcname(arg, arg, ...) -``` - -embedded in any text where any arg can be another $funcname{} call. -This functionality is turned off by default - to activate, -`settings.INLINEFUNC_ENABLED` must be set to `True`. - -Each token starts with "$funcname(" where there must be no space -between the $funcname and (. It ends with a matched ending parentesis. -")". - -Inside the inlinefunc definition, one can use `\` to escape. This is -mainly needed for escaping commas in flowing text (which would -otherwise be interpreted as an argument separator), or to escape `}` -when not intended to close the function block. Enclosing text in -matched `\"\"\"` (triple quotes) or `'''` (triple single-quotes) will -also escape *everything* within without needing to escape individual -characters. - -The available inlinefuncs are defined as global-level functions in -modules defined by `settings.INLINEFUNC_MODULES`. They are identified -by their function name (and ignored if this name starts with `_`). They -should be on the following form: - -```python -def funcname (*args, **kwargs): - # ... -``` - -Here, the arguments given to `$funcname(arg1,arg2)` will appear as the -`*args` tuple. This will be populated by the arguments given to the -inlinefunc in-game - the only part that will be available from -in-game. `**kwargs` are not supported from in-game but are only used -internally by Evennia to make details about the caller available to -the function. The kwarg passed to all functions is `session`, the -Sessionobject for the object seeing the string. This may be `None` if -the string is sent to a non-puppetable object. The inlinefunc should -never raise an exception. - -There are two reserved function names: -- "nomatch": This is called if the user uses a functionname that is - not registered. The nomatch function will get the name of the - not-found function as its first argument followed by the normal - arguments to the given function. If not defined the default effect is - to print `` to replace the unknown function. -- "stackfull": This is called when the maximum nested function stack is reached. - When this happens, the original parsed string is returned and the result of - the `stackfull` inlinefunc is appended to the end. By default this is an - error message. - -Error handling: - Syntax errors, notably not completely closing all inlinefunc - blocks, will lead to the entire string remaining unparsed. - -""" - -import re -from django.conf import settings -from evennia.utils import utils - - -# example/testing inline functions - -def pad(*args, **kwargs): - """ - Inlinefunc. Pads text to given width. - - Args: - text (str, optional): Text to pad. - width (str, optional): Will be converted to integer. Width - of padding. - align (str, optional): Alignment of padding; one of 'c', 'l' or 'r'. - fillchar (str, optional): Character used for padding. Defaults to a space. - - Kwargs: - session (Session): Session performing the pad. - - Example: - `$pad(text, width, align, fillchar)` - - """ - text, width, align, fillchar = "", 78, 'c', ' ' - nargs = len(args) - if nargs > 0: - text = args[0] - if nargs > 1: - width = int(args[1]) if args[1].strip().isdigit() else 78 - if nargs > 2: - align = args[2] if args[2] in ('c', 'l', 'r') else 'c' - if nargs > 3: - fillchar = args[3] - return utils.pad(text, width=width, align=align, fillchar=fillchar) - - -def crop(*args, **kwargs): - """ - Inlinefunc. Crops ingoing text to given widths. - - Args: - text (str, optional): Text to crop. - width (str, optional): Will be converted to an integer. Width of - crop in characters. - suffix (str, optional): End string to mark the fact that a part - of the string was cropped. Defaults to `[...]`. - Kwargs: - session (Session): Session performing the crop. - - Example: - `$crop(text, width=78, suffix='[...]')` - - """ - text, width, suffix = "", 78, "[...]" - nargs = len(args) - if nargs > 0: - text = args[0] - if nargs > 1: - width = int(args[1]) if args[1].strip().isdigit() else 78 - if nargs > 2: - suffix = args[2] - return utils.crop(text, width=width, suffix=suffix) - - -def clr(*args, **kwargs): - """ - Inlinefunc. Colorizes nested text. - - Args: - startclr (str, optional): An ANSI color abbreviation without the - prefix `|`, such as `r` (red foreground) or `[r` (red background). - text (str, optional): Text - endclr (str, optional): The color to use at the end of the string. Defaults - to `|n` (reset-color). - Kwargs: - session (Session): Session object triggering inlinefunc. - - Example: - `$clr(startclr, text, endclr)` - - """ - text = "" - nargs = len(args) - if nargs > 0: - color = args[0].strip() - if nargs > 1: - text = args[1] - text = "|" + color + text - if nargs > 2: - text += "|" + args[2].strip() - else: - text += "|n" - return text - - -# we specify a default nomatch function to use if no matching func was -# found. This will be overloaded by any nomatch function defined in -# the imported modules. -_INLINE_FUNCS = {"nomatch": lambda *args, **kwargs: "", - "stackfull": lambda *args, **kwargs: "\n (not parsed: inlinefunc stack size exceeded.)"} - - -# load custom inline func modules. -for module in utils.make_iter(settings.INLINEFUNC_MODULES): - try: - _INLINE_FUNCS.update(utils.callables_from_module(module)) - except ImportError as err: - if module == "server.conf.inlinefuncs": - # a temporary warning since the default module changed name - raise ImportError("Error: %s\nPossible reason: mygame/server/conf/inlinefunc.py should " - "be renamed to mygame/server/conf/inlinefuncs.py (note the S at the end)." % err) - else: - raise - - -# remove the core function if we include examples in this module itself -#_INLINE_FUNCS.pop("inline_func_parse", None) - - -# The stack size is a security measure. Set to <=0 to disable. -try: - _STACK_MAXSIZE = settings.INLINEFUNC_STACK_MAXSIZE -except AttributeError: - _STACK_MAXSIZE = 20 - -# regex definitions - -_RE_STARTTOKEN = re.compile(r"(?.*?)(?.*?)(?(?(?(?\\'|\\"|\\\)|\\$\w+\()| # escaped tokens should re-appear in text - (?P[\w\s.-\/#!%\^&\*;:=\-_`~\|\(}{\[\]]+|\"{1}|\'{1}) # everything else should also be included""", - re.UNICODE + re.IGNORECASE + re.VERBOSE + re.DOTALL) - - -# Cache for function lookups. -_PARSING_CACHE = utils.LimitedSizeOrderedDict(size_limit=1000) - - -class ParseStack(list): - """ - Custom stack that always concatenates strings together when the - strings are added next to one another. Tuples are stored - separately and None is used to mark that a string should be broken - up into a new chunk. Below is the resulting stack after separately - appending 3 strings, None, 2 strings, a tuple and finally 2 - strings: - - [string + string + string, - None - string + string, - tuple, - string + string] - - """ - - def __init__(self, *args, **kwargs): - super(ParseStack, self).__init__(*args, **kwargs) - # always start stack with the empty string - list.append(self, "") - # indicates if the top of the stack is a string or not - self._string_last = True - - def __eq__(self, other): - return (super(ParseStack).__eq__(other) and - hasattr(other, "_string_last") and self._string_last == other._string_last) - - def __ne__(self, other): - return not self.__eq__(other) - - def append(self, item): - """ - The stack will merge strings, add other things as normal - """ - if isinstance(item, basestring): - if self._string_last: - self[-1] += item - else: - list.append(self, item) - self._string_last = True - else: - # everything else is added as normal - list.append(self, item) - self._string_last = False - - -class InlinefuncError(RuntimeError): - pass - - -def parse_inlinefunc(string, strip=False, **kwargs): - """ - Parse the incoming string. - - Args: - string (str): The incoming string to parse. - strip (bool, optional): Whether to strip function calls rather than - execute them. - Kwargs: - session (Session): This is sent to this function by Evennia when triggering - it. It is passed to the inlinefunc. - kwargs (any): All other kwargs are also passed on to the inlinefunc. - - - """ - global _PARSING_CACHE - if string in _PARSING_CACHE: - # stack is already cached - stack = _PARSING_CACHE[string] - elif not _RE_STARTTOKEN.search(string): - # if there are no unescaped start tokens at all, return immediately. - return string - else: - # no cached stack; build a new stack and continue - stack = ParseStack() - - # process string on stack - ncallable = 0 - for match in _RE_TOKEN.finditer(string): - gdict = match.groupdict() - if gdict["singlequote"]: - stack.append(gdict["singlequote"]) - elif gdict["doublequote"]: - stack.append(gdict["doublequote"]) - elif gdict["end"]: - if ncallable <= 0: - stack.append(")") - continue - args = [] - while stack: - operation = stack.pop() - if callable(operation): - if not strip: - stack.append((operation, [arg for arg in reversed(args)])) - ncallable -= 1 - break - else: - args.append(operation) - elif gdict["start"]: - funcname = _RE_STARTTOKEN.match(gdict["start"]).group(1) - try: - # try to fetch the matching inlinefunc from storage - stack.append(_INLINE_FUNCS[funcname]) - except KeyError: - stack.append(_INLINE_FUNCS["nomatch"]) - stack.append(funcname) - ncallable += 1 - elif gdict["escaped"]: - # escaped tokens - token = gdict["escaped"].lstrip("\\") - stack.append(token) - elif gdict["comma"]: - if ncallable > 0: - # commas outside strings and inside a callable are - # used to mark argument separation - we use None - # in the stack to indicate such a separation. - stack.append(None) - else: - # no callable active - just a string - stack.append(",") - else: - # the rest - stack.append(gdict["rest"]) - - if ncallable > 0: - # this means not all inlinefuncs were complete - return string - - if _STACK_MAXSIZE > 0 and _STACK_MAXSIZE < len(stack): - # if stack is larger than limit, throw away parsing - return string + gdict["stackfull"](*args, **kwargs) - else: - # cache the stack - _PARSING_CACHE[string] = stack - - # run the stack recursively - def _run_stack(item, depth=0): - retval = item - if isinstance(item, tuple): - if strip: - return "" - else: - func, arglist = item - args = [""] - for arg in arglist: - if arg is None: - # an argument-separating comma - start a new arg - args.append("") - else: - # all other args should merge into one string - args[-1] += _run_stack(arg, depth=depth + 1) - # execute the inlinefunc at this point or strip it. - kwargs["inlinefunc_stack_depth"] = depth - retval = "" if strip else func(*args, **kwargs) - return utils.to_str(retval, force_string=True) - - # execute the stack from the cache - return "".join(_run_stack(item) for item in _PARSING_CACHE[string]) - -# -# Nick templating -# - - -""" -This supports the use of replacement templates in nicks: - -This happens in two steps: - -1) The user supplies a template that is converted to a regex according - to the unix-like templating language. -2) This regex is tested against nicks depending on which nick replacement - strategy is considered (most commonly inputline). -3) If there is a template match and there are templating markers, - these are replaced with the arguments actually given. - -@desc $1 $2 $3 - -This will be converted to the following regex: - -\@desc (?P<1>\w+) (?P<2>\w+) $(?P<3>\w+) - -Supported template markers (through fnmatch) - * matches anything (non-greedy) -> .*? - ? matches any single character -> - [seq] matches any entry in sequence - [!seq] matches entries not in sequence -Custom arg markers - $N argument position (1-99) - -""" -import fnmatch -_RE_NICK_ARG = re.compile(r"\\(\$)([1-9][0-9]?)") -_RE_NICK_TEMPLATE_ARG = re.compile(r"(\$)([1-9][0-9]?)") -_RE_NICK_SPACE = re.compile(r"\\ ") - - -class NickTemplateInvalid(ValueError): - pass - - -def initialize_nick_templates(in_template, out_template): - """ - Initialize the nick templates for matching and remapping a string. - - Args: - in_template (str): The template to be used for nick recognition. - out_template (str): The template to be used to replace the string - matched by the in_template. - - Returns: - regex (regex): Regex to match against strings - template (str): Template with markers {arg1}, {arg2}, etc for - replacement using the standard .format method. - - Raises: - NickTemplateInvalid: If the in/out template does not have a matching - number of $args. - - """ - # create the regex for in_template - regex_string = fnmatch.translate(in_template) - n_inargs = len(_RE_NICK_ARG.findall(regex_string)) - regex_string = _RE_NICK_SPACE.sub("\s+", regex_string) - regex_string = _RE_NICK_ARG.sub(lambda m: "(?P.+?)" % m.group(2), regex_string) - - # create the out_template - template_string = _RE_NICK_TEMPLATE_ARG.sub(lambda m: "{arg%s}" % m.group(2), out_template) - - # validate the tempaltes - they should at least have the same number of args - n_outargs = len(_RE_NICK_TEMPLATE_ARG.findall(out_template)) - if n_inargs != n_outargs: - print n_inargs, n_outargs - raise NickTemplateInvalid - - return re.compile(regex_string), template_string - - -def parse_nick_template(string, template_regex, outtemplate): - """ - Parse a text using a template and map it to another template - - Args: - string (str): The input string to processj - template_regex (regex): A template regex created with - initialize_nick_template. - outtemplate (str): The template to which to map the matches - produced by the template_regex. This should have $1, $2, - etc to match the regex. - - """ - match = template_regex.match(string) - if match: - return outtemplate.format(**match.groupdict()) - return string +""" +Inline functions (nested form). + +This parser accepts nested inlinefunctions on the form + +``` +$funcname(arg, arg, ...) +``` + +embedded in any text where any arg can be another $funcname{} call. +This functionality is turned off by default - to activate, +`settings.INLINEFUNC_ENABLED` must be set to `True`. + +Each token starts with "$funcname(" where there must be no space +between the $funcname and (. It ends with a matched ending parentesis. +")". + +Inside the inlinefunc definition, one can use `\` to escape. This is +mainly needed for escaping commas in flowing text (which would +otherwise be interpreted as an argument separator), or to escape `}` +when not intended to close the function block. Enclosing text in +matched `\"\"\"` (triple quotes) or `'''` (triple single-quotes) will +also escape *everything* within without needing to escape individual +characters. + +The available inlinefuncs are defined as global-level functions in +modules defined by `settings.INLINEFUNC_MODULES`. They are identified +by their function name (and ignored if this name starts with `_`). They +should be on the following form: + +```python +def funcname (*args, **kwargs): + # ... +``` + +Here, the arguments given to `$funcname(arg1,arg2)` will appear as the +`*args` tuple. This will be populated by the arguments given to the +inlinefunc in-game - the only part that will be available from +in-game. `**kwargs` are not supported from in-game but are only used +internally by Evennia to make details about the caller available to +the function. The kwarg passed to all functions is `session`, the +Sessionobject for the object seeing the string. This may be `None` if +the string is sent to a non-puppetable object. The inlinefunc should +never raise an exception. + +There are two reserved function names: +- "nomatch": This is called if the user uses a functionname that is + not registered. The nomatch function will get the name of the + not-found function as its first argument followed by the normal + arguments to the given function. If not defined the default effect is + to print `` to replace the unknown function. +- "stackfull": This is called when the maximum nested function stack is reached. + When this happens, the original parsed string is returned and the result of + the `stackfull` inlinefunc is appended to the end. By default this is an + error message. + +Error handling: + Syntax errors, notably not completely closing all inlinefunc + blocks, will lead to the entire string remaining unparsed. + +""" + +import re +from django.conf import settings +from evennia.utils import utils + + +# example/testing inline functions + +def pad(*args, **kwargs): + """ + Inlinefunc. Pads text to given width. + + Args: + text (str, optional): Text to pad. + width (str, optional): Will be converted to integer. Width + of padding. + align (str, optional): Alignment of padding; one of 'c', 'l' or 'r'. + fillchar (str, optional): Character used for padding. Defaults to a space. + + Kwargs: + session (Session): Session performing the pad. + + Example: + `$pad(text, width, align, fillchar)` + + """ + text, width, align, fillchar = "", 78, 'c', ' ' + nargs = len(args) + if nargs > 0: + text = args[0] + if nargs > 1: + width = int(args[1]) if args[1].strip().isdigit() else 78 + if nargs > 2: + align = args[2] if args[2] in ('c', 'l', 'r') else 'c' + if nargs > 3: + fillchar = args[3] + return utils.pad(text, width=width, align=align, fillchar=fillchar) + + +def crop(*args, **kwargs): + """ + Inlinefunc. Crops ingoing text to given widths. + + Args: + text (str, optional): Text to crop. + width (str, optional): Will be converted to an integer. Width of + crop in characters. + suffix (str, optional): End string to mark the fact that a part + of the string was cropped. Defaults to `[...]`. + Kwargs: + session (Session): Session performing the crop. + + Example: + `$crop(text, width=78, suffix='[...]')` + + """ + text, width, suffix = "", 78, "[...]" + nargs = len(args) + if nargs > 0: + text = args[0] + if nargs > 1: + width = int(args[1]) if args[1].strip().isdigit() else 78 + if nargs > 2: + suffix = args[2] + return utils.crop(text, width=width, suffix=suffix) + + +def clr(*args, **kwargs): + """ + Inlinefunc. Colorizes nested text. + + Args: + startclr (str, optional): An ANSI color abbreviation without the + prefix `|`, such as `r` (red foreground) or `[r` (red background). + text (str, optional): Text + endclr (str, optional): The color to use at the end of the string. Defaults + to `|n` (reset-color). + Kwargs: + session (Session): Session object triggering inlinefunc. + + Example: + `$clr(startclr, text, endclr)` + + """ + text = "" + nargs = len(args) + if nargs > 0: + color = args[0].strip() + if nargs > 1: + text = args[1] + text = "|" + color + text + if nargs > 2: + text += "|" + args[2].strip() + else: + text += "|n" + return text + + +# we specify a default nomatch function to use if no matching func was +# found. This will be overloaded by any nomatch function defined in +# the imported modules. +_INLINE_FUNCS = {"nomatch": lambda *args, **kwargs: "", + "stackfull": lambda *args, **kwargs: "\n (not parsed: inlinefunc stack size exceeded.)"} + + +# load custom inline func modules. +for module in utils.make_iter(settings.INLINEFUNC_MODULES): + try: + _INLINE_FUNCS.update(utils.callables_from_module(module)) + except ImportError as err: + if module == "server.conf.inlinefuncs": + # a temporary warning since the default module changed name + raise ImportError("Error: %s\nPossible reason: mygame/server/conf/inlinefunc.py should " + "be renamed to mygame/server/conf/inlinefuncs.py (note the S at the end)." % err) + else: + raise + + +# remove the core function if we include examples in this module itself +#_INLINE_FUNCS.pop("inline_func_parse", None) + + +# The stack size is a security measure. Set to <=0 to disable. +try: + _STACK_MAXSIZE = settings.INLINEFUNC_STACK_MAXSIZE +except AttributeError: + _STACK_MAXSIZE = 20 + +# regex definitions + +_RE_STARTTOKEN = re.compile(r"(?.*?)(?.*?)(?(?(?(?\\'|\\"|\\\)|\\$\w+\()| # escaped tokens should re-appear in text + (?P[\w\s.-\/#!%\^&\*;:=\-_`~\|\(}{\[\]]+|\"{1}|\'{1}) # everything else should also be included""", + re.UNICODE + re.IGNORECASE + re.VERBOSE + re.DOTALL) + + +# Cache for function lookups. +_PARSING_CACHE = utils.LimitedSizeOrderedDict(size_limit=1000) + + +class ParseStack(list): + """ + Custom stack that always concatenates strings together when the + strings are added next to one another. Tuples are stored + separately and None is used to mark that a string should be broken + up into a new chunk. Below is the resulting stack after separately + appending 3 strings, None, 2 strings, a tuple and finally 2 + strings: + + [string + string + string, + None + string + string, + tuple, + string + string] + + """ + + def __init__(self, *args, **kwargs): + super(ParseStack, self).__init__(*args, **kwargs) + # always start stack with the empty string + list.append(self, "") + # indicates if the top of the stack is a string or not + self._string_last = True + + def __eq__(self, other): + return (super(ParseStack).__eq__(other) and + hasattr(other, "_string_last") and self._string_last == other._string_last) + + def __ne__(self, other): + return not self.__eq__(other) + + def append(self, item): + """ + The stack will merge strings, add other things as normal + """ + if isinstance(item, str): + if self._string_last: + self[-1] += item + else: + list.append(self, item) + self._string_last = True + else: + # everything else is added as normal + list.append(self, item) + self._string_last = False + + +class InlinefuncError(RuntimeError): + pass + + +def parse_inlinefunc(string, strip=False, **kwargs): + """ + Parse the incoming string. + + Args: + string (str): The incoming string to parse. + strip (bool, optional): Whether to strip function calls rather than + execute them. + Kwargs: + session (Session): This is sent to this function by Evennia when triggering + it. It is passed to the inlinefunc. + kwargs (any): All other kwargs are also passed on to the inlinefunc. + + + """ + global _PARSING_CACHE + if string in _PARSING_CACHE: + # stack is already cached + stack = _PARSING_CACHE[string] + elif not _RE_STARTTOKEN.search(string): + # if there are no unescaped start tokens at all, return immediately. + return string + else: + # no cached stack; build a new stack and continue + stack = ParseStack() + + # process string on stack + ncallable = 0 + for match in _RE_TOKEN.finditer(string): + gdict = match.groupdict() + if gdict["singlequote"]: + stack.append(gdict["singlequote"]) + elif gdict["doublequote"]: + stack.append(gdict["doublequote"]) + elif gdict["end"]: + if ncallable <= 0: + stack.append(")") + continue + args = [] + while stack: + operation = stack.pop() + if callable(operation): + if not strip: + stack.append((operation, [arg for arg in reversed(args)])) + ncallable -= 1 + break + else: + args.append(operation) + elif gdict["start"]: + funcname = _RE_STARTTOKEN.match(gdict["start"]).group(1) + try: + # try to fetch the matching inlinefunc from storage + stack.append(_INLINE_FUNCS[funcname]) + except KeyError: + stack.append(_INLINE_FUNCS["nomatch"]) + stack.append(funcname) + ncallable += 1 + elif gdict["escaped"]: + # escaped tokens + token = gdict["escaped"].lstrip("\\") + stack.append(token) + elif gdict["comma"]: + if ncallable > 0: + # commas outside strings and inside a callable are + # used to mark argument separation - we use None + # in the stack to indicate such a separation. + stack.append(None) + else: + # no callable active - just a string + stack.append(",") + else: + # the rest + stack.append(gdict["rest"]) + + if ncallable > 0: + # this means not all inlinefuncs were complete + return string + + if _STACK_MAXSIZE > 0 and _STACK_MAXSIZE < len(stack): + # if stack is larger than limit, throw away parsing + return string + gdict["stackfull"](*args, **kwargs) + else: + # cache the stack + _PARSING_CACHE[string] = stack + + # run the stack recursively + def _run_stack(item, depth=0): + retval = item + if isinstance(item, tuple): + if strip: + return "" + else: + func, arglist = item + args = [""] + for arg in arglist: + if arg is None: + # an argument-separating comma - start a new arg + args.append("") + else: + # all other args should merge into one string + args[-1] += _run_stack(arg, depth=depth + 1) + # execute the inlinefunc at this point or strip it. + kwargs["inlinefunc_stack_depth"] = depth + retval = "" if strip else func(*args, **kwargs) + return utils.to_str(retval, force_string=True) + + # execute the stack from the cache + return "".join(_run_stack(item) for item in _PARSING_CACHE[string]) + +# +# Nick templating +# + + +""" +This supports the use of replacement templates in nicks: + +This happens in two steps: + +1) The user supplies a template that is converted to a regex according + to the unix-like templating language. +2) This regex is tested against nicks depending on which nick replacement + strategy is considered (most commonly inputline). +3) If there is a template match and there are templating markers, + these are replaced with the arguments actually given. + +@desc $1 $2 $3 + +This will be converted to the following regex: + +\@desc (?P<1>\w+) (?P<2>\w+) $(?P<3>\w+) + +Supported template markers (through fnmatch) + * matches anything (non-greedy) -> .*? + ? matches any single character -> + [seq] matches any entry in sequence + [!seq] matches entries not in sequence +Custom arg markers + $N argument position (1-99) + +""" +import fnmatch +_RE_NICK_ARG = re.compile(r"\\(\$)([1-9][0-9]?)") +_RE_NICK_TEMPLATE_ARG = re.compile(r"(\$)([1-9][0-9]?)") +_RE_NICK_SPACE = re.compile(r"\\ ") + + +class NickTemplateInvalid(ValueError): + pass + + +def initialize_nick_templates(in_template, out_template): + """ + Initialize the nick templates for matching and remapping a string. + + Args: + in_template (str): The template to be used for nick recognition. + out_template (str): The template to be used to replace the string + matched by the in_template. + + Returns: + regex (regex): Regex to match against strings + template (str): Template with markers {arg1}, {arg2}, etc for + replacement using the standard .format method. + + Raises: + NickTemplateInvalid: If the in/out template does not have a matching + number of $args. + + """ + # create the regex for in_template + regex_string = fnmatch.translate(in_template) + n_inargs = len(_RE_NICK_ARG.findall(regex_string)) + regex_string = _RE_NICK_SPACE.sub("\s+", regex_string) + regex_string = _RE_NICK_ARG.sub(lambda m: "(?P.+?)" % m.group(2), regex_string) + + # create the out_template + template_string = _RE_NICK_TEMPLATE_ARG.sub(lambda m: "{arg%s}" % m.group(2), out_template) + + # validate the tempaltes - they should at least have the same number of args + n_outargs = len(_RE_NICK_TEMPLATE_ARG.findall(out_template)) + if n_inargs != n_outargs: + print(n_inargs, n_outargs) + raise NickTemplateInvalid + + return re.compile(regex_string), template_string + + +def parse_nick_template(string, template_regex, outtemplate): + """ + Parse a text using a template and map it to another template + + Args: + string (str): The input string to processj + template_regex (regex): A template regex created with + initialize_nick_template. + outtemplate (str): The template to which to map the matches + produced by the template_regex. This should have $1, $2, + etc to match the regex. + + """ + match = template_regex.match(string) + if match: + return outtemplate.format(**match.groupdict()) + return string diff --git a/evennia/utils/logger.py b/evennia/utils/logger.py index ce3bfe9a15..feb76e949c 100644 --- a/evennia/utils/logger.py +++ b/evennia/utils/logger.py @@ -13,7 +13,7 @@ log_typemsg(). This is for historical, back-compatible reasons. """ -from __future__ import division + import os import time diff --git a/evennia/utils/picklefield.py b/evennia/utils/picklefield.py index bd0a9b1643..61ed452c19 100644 --- a/evennia/utils/picklefield.py +++ b/evennia/utils/picklefield.py @@ -54,7 +54,7 @@ except ImportError: # python 3.x does not have cPickle module try: - from cPickle import loads, dumps # cpython 2.x + from pickle import loads, dumps # cpython 2.x except ImportError: from pickle import loads, dumps # cpython 3.x, other interpreters diff --git a/evennia/utils/spawner.py b/evennia/utils/spawner.py index 6df11985f1..1afff3413d 100644 --- a/evennia/utils/spawner.py +++ b/evennia/utils/spawner.py @@ -87,7 +87,7 @@ otherwise have the same spells as a *goblin wizard* who in turn shares many traits with a normal *goblin*. """ -from __future__ import print_function + import copy from django.conf import settings @@ -235,10 +235,10 @@ def spawn(*prototypes, **kwargs): protmodules = make_iter(settings.PROTOTYPE_MODULES) for prototype_module in protmodules: protparents.update(dict((key, val) for key, val in - all_from_module(prototype_module).items() if isinstance(val, dict))) + list(all_from_module(prototype_module).items()) if isinstance(val, dict))) # overload module's protparents with specifically given protparents protparents.update(kwargs.get("prototype_parents", {})) - for key, prototype in protparents.items(): + for key, prototype in list(protparents.items()): _validate_prototype(key, prototype, protparents, []) if "return_prototypes" in kwargs: @@ -288,11 +288,11 @@ def spawn(*prototypes, **kwargs): # extract ndb assignments nattributes = dict((key.split("_", 1)[1], value() if callable(value) else value) - for key, value in prot.items() if key.startswith("ndb_")) + for key, value in list(prot.items()) if key.startswith("ndb_")) # the rest are attributes simple_attributes = [(key, value()) if callable(value) else (key, value) - for key, value in prot.items() if not key.startswith("ndb_")] + for key, value in list(prot.items()) if not key.startswith("ndb_")] attributes = attributes + simple_attributes attributes = [tup for tup in attributes if not tup[0] in _CREATE_OBJECT_KWARGS] diff --git a/evennia/utils/tests/test_evform.py b/evennia/utils/tests/test_evform.py index e6a0d26049..a02867b221 100644 --- a/evennia/utils/tests/test_evform.py +++ b/evennia/utils/tests/test_evform.py @@ -10,46 +10,46 @@ class TestEvForm(TestCase): def test_form(self): self.maxDiff = None self.assertEqual(evform._test(), - u'.------------------------------------------------.\n' - u'| |\n' - u'| Name: \x1b[0m\x1b[1m\x1b[32mTom\x1b[1m\x1b[32m \x1b' - u'[1m\x1b[32mthe\x1b[1m\x1b[32m \x1b[0m \x1b[0m ' - u'Account: \x1b[0m\x1b[1m\x1b[33mGriatch ' - u'\x1b[0m\x1b[0m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[0m\x1b[0m ' - u'|\n' - u'| \x1b[0m\x1b[1m\x1b[32mBouncer\x1b[0m \x1b[0m |\n' - u'| |\n' - u' >----------------------------------------------<\n' - u'| |\n' - u'| Desc: \x1b[0mA sturdy \x1b[0m \x1b[0m' - u' STR: \x1b[0m12 \x1b[0m\x1b[0m\x1b[0m\x1b[0m' - u' DEX: \x1b[0m10 \x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' - u'| \x1b[0mfellow\x1b[0m \x1b[0m' - u' INT: \x1b[0m5 \x1b[0m\x1b[0m\x1b[0m\x1b[0m' - u' STA: \x1b[0m18 \x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' - u'| \x1b[0m \x1b[0m' - u' LUC: \x1b[0m10 \x1b[0m\x1b[0m\x1b[0m' - u' MAG: \x1b[0m3 \x1b[0m\x1b[0m\x1b[0m |\n' - u'| |\n' - u' >----------.-----------------------------------<\n' - u'| | |\n' - u'| \x1b[0mHP\x1b[0m|\x1b[0mMV \x1b[0m|\x1b[0mMP\x1b[0m ' - u'| \x1b[0mSkill \x1b[0m|\x1b[0mValue \x1b[0m' - u'|\x1b[0mExp \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' - u'| ~~+~~~+~~ | ~~~~~~~~~~~+~~~~~~~~~~+~~~~~~~~~~~ |\n' - u'| \x1b[0m**\x1b[0m|\x1b[0m***\x1b[0m\x1b[0m|\x1b[0m**\x1b[0m\x1b[0m ' - u'| \x1b[0mShooting \x1b[0m|\x1b[0m12 \x1b[0m' - u'|\x1b[0m550/1200 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' - u'| \x1b[0m \x1b[0m|\x1b[0m**\x1b[0m \x1b[0m|\x1b[0m*\x1b[0m \x1b[0m ' - u'| \x1b[0mHerbalism \x1b[0m|\x1b[0m14 \x1b[0m' - u'|\x1b[0m990/1400 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' - u'| \x1b[0m \x1b[0m|\x1b[0m \x1b[0m|\x1b[0m \x1b[0m ' - u'| \x1b[0mSmithing \x1b[0m|\x1b[0m9 \x1b[0m' - u'|\x1b[0m205/900 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' - u'| | |\n' - u' -----------`-------------------------------------\n') + '.------------------------------------------------.\n' + '| |\n' + '| Name: \x1b[0m\x1b[1m\x1b[32mTom\x1b[1m\x1b[32m \x1b' + '[1m\x1b[32mthe\x1b[1m\x1b[32m \x1b[0m \x1b[0m ' + 'Account: \x1b[0m\x1b[1m\x1b[33mGriatch ' + '\x1b[0m\x1b[0m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[0m\x1b[0m ' + '|\n' + '| \x1b[0m\x1b[1m\x1b[32mBouncer\x1b[0m \x1b[0m |\n' + '| |\n' + ' >----------------------------------------------<\n' + '| |\n' + '| Desc: \x1b[0mA sturdy \x1b[0m \x1b[0m' + ' STR: \x1b[0m12 \x1b[0m\x1b[0m\x1b[0m\x1b[0m' + ' DEX: \x1b[0m10 \x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' + '| \x1b[0mfellow\x1b[0m \x1b[0m' + ' INT: \x1b[0m5 \x1b[0m\x1b[0m\x1b[0m\x1b[0m' + ' STA: \x1b[0m18 \x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' + '| \x1b[0m \x1b[0m' + ' LUC: \x1b[0m10 \x1b[0m\x1b[0m\x1b[0m' + ' MAG: \x1b[0m3 \x1b[0m\x1b[0m\x1b[0m |\n' + '| |\n' + ' >----------.-----------------------------------<\n' + '| | |\n' + '| \x1b[0mHP\x1b[0m|\x1b[0mMV \x1b[0m|\x1b[0mMP\x1b[0m ' + '| \x1b[0mSkill \x1b[0m|\x1b[0mValue \x1b[0m' + '|\x1b[0mExp \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' + '| ~~+~~~+~~ | ~~~~~~~~~~~+~~~~~~~~~~+~~~~~~~~~~~ |\n' + '| \x1b[0m**\x1b[0m|\x1b[0m***\x1b[0m\x1b[0m|\x1b[0m**\x1b[0m\x1b[0m ' + '| \x1b[0mShooting \x1b[0m|\x1b[0m12 \x1b[0m' + '|\x1b[0m550/1200 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' + '| \x1b[0m \x1b[0m|\x1b[0m**\x1b[0m \x1b[0m|\x1b[0m*\x1b[0m \x1b[0m ' + '| \x1b[0mHerbalism \x1b[0m|\x1b[0m14 \x1b[0m' + '|\x1b[0m990/1400 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' + '| \x1b[0m \x1b[0m|\x1b[0m \x1b[0m|\x1b[0m \x1b[0m ' + '| \x1b[0mSmithing \x1b[0m|\x1b[0m9 \x1b[0m' + '|\x1b[0m205/900 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' + '| | |\n' + ' -----------`-------------------------------------\n') def test_ansi_escape(self): # note that in a msg() call, the result would be the correct |-----, # in a print, ansi only gets called once, so ||----- is the result - self.assertEqual(unicode(evform.EvForm(form={"FORM": "\n||-----"})), "||-----") + self.assertEqual(str(evform.EvForm(form={"FORM": "\n||-----"})), "||-----") diff --git a/evennia/utils/tests/test_evmenu.py b/evennia/utils/tests/test_evmenu.py index 04310c90ed..278bbeec1c 100644 --- a/evennia/utils/tests/test_evmenu.py +++ b/evennia/utils/tests/test_evmenu.py @@ -58,7 +58,7 @@ class TestEvMenu(TestCase): def _debug_output(self, indent, msg): if self.debug_output: - print(" " * indent + msg) + print((" " * indent + msg)) def _test_menutree(self, menu): """ diff --git a/evennia/utils/tests/test_tagparsing.py b/evennia/utils/tests/test_tagparsing.py index a2f07af204..520347a919 100644 --- a/evennia/utils/tests/test_tagparsing.py +++ b/evennia/utils/tests/test_tagparsing.py @@ -15,8 +15,8 @@ class ANSIStringTestCase(TestCase): Verifies the raw and clean strings of an ANSIString match expected output. """ - self.assertEqual(unicode(ansi.clean()), clean) - self.assertEqual(unicode(ansi.raw()), raw) + self.assertEqual(str(ansi.clean()), clean) + self.assertEqual(str(ansi.raw()), raw) def table_check(self, ansi, char, code): """ @@ -29,8 +29,8 @@ class ANSIStringTestCase(TestCase): """ Make sure the ANSIString is always constructed correctly. """ - clean = u'This isA|r testTest' - encoded = u'\x1b[1m\x1b[32mThis is\x1b[1m\x1b[31mA|r test\x1b[0mTest\x1b[0m' + clean = 'This isA|r testTest' + encoded = '\x1b[1m\x1b[32mThis is\x1b[1m\x1b[31mA|r test\x1b[0mTest\x1b[0m' target = ANSIString(r'|gThis is|rA||r test|nTest|n') char_table = [9, 10, 11, 12, 13, 14, 15, 25, 26, 27, 28, 29, 30, 31, 32, 37, 38, 39, 40] code_table = [0, 1, 2, 3, 4, 5, 6, 7, 8, 16, 17, 18, 19, 20, 21, 22, 23, 24, 33, 34, 35, 36, 41, 42, 43, 44] @@ -41,9 +41,9 @@ class ANSIStringTestCase(TestCase): self.checker(ANSIString(encoded, decoded=True), encoded, clean) self.table_check(ANSIString(encoded, decoded=True), char_table, code_table) - self.checker(ANSIString('Test'), u'Test', u'Test') + self.checker(ANSIString('Test'), 'Test', 'Test') self.table_check(ANSIString('Test'), [0, 1, 2, 3], []) - self.checker(ANSIString(''), u'', u'') + self.checker(ANSIString(''), '', '') def test_slice(self): """ @@ -52,24 +52,24 @@ class ANSIStringTestCase(TestCase): """ target = ANSIString(r'|gTest|rTest|n') result = target[:3] - self.checker(result, u'\x1b[1m\x1b[32mTes', u'Tes') + self.checker(result, '\x1b[1m\x1b[32mTes', 'Tes') result = target[:4] - self.checker(result, u'\x1b[1m\x1b[32mTest\x1b[1m\x1b[31m', u'Test') + self.checker(result, '\x1b[1m\x1b[32mTest\x1b[1m\x1b[31m', 'Test') result = target[:] self.checker( result, - u'\x1b[1m\x1b[32mTest\x1b[1m\x1b[31mTest\x1b[0m', - u'TestTest') + '\x1b[1m\x1b[32mTest\x1b[1m\x1b[31mTest\x1b[0m', + 'TestTest') result = target[:-1] self.checker( result, - u'\x1b[1m\x1b[32mTest\x1b[1m\x1b[31mTes', - u'TestTes') + '\x1b[1m\x1b[32mTest\x1b[1m\x1b[31mTes', + 'TestTes') result = target[0:0] self.checker( result, - u'', - u'') + '', + '') def test_split(self): """ @@ -77,9 +77,9 @@ class ANSIStringTestCase(TestCase): codes end up where they should. """ target = ANSIString("|gThis is |nA split string|g") - first = (u'\x1b[1m\x1b[32mThis is \x1b[0m', u'This is ') - second = (u'\x1b[1m\x1b[32m\x1b[0m split string\x1b[1m\x1b[32m', - u' split string') + first = ('\x1b[1m\x1b[32mThis is \x1b[0m', 'This is ') + second = ('\x1b[1m\x1b[32m\x1b[0m split string\x1b[1m\x1b[32m', + ' split string') re_split = re.split('A', target) normal_split = target.split('A') self.assertEqual(re_split, normal_split) @@ -97,11 +97,11 @@ class ANSIStringTestCase(TestCase): l = [ANSIString("|gTest|r") for _ in range(0, 3)] # Force the generator to be evaluated. result = "".join(l) - self.assertEqual(unicode(result), u'TestTestTest') + self.assertEqual(str(result), 'TestTestTest') result = ANSIString("").join(l) - self.checker(result, u'\x1b[1m\x1b[32mTest\x1b[1m\x1b[31m\x1b[1m\x1b' - u'[32mTest\x1b[1m\x1b[31m\x1b[1m\x1b[32mTest' - u'\x1b[1m\x1b[31m', u'TestTestTest') + self.checker(result, '\x1b[1m\x1b[32mTest\x1b[1m\x1b[31m\x1b[1m\x1b' + '[32mTest\x1b[1m\x1b[31m\x1b[1m\x1b[32mTest' + '\x1b[1m\x1b[31m', 'TestTestTest') def test_len(self): """ @@ -116,8 +116,8 @@ class ANSIStringTestCase(TestCase): _transform functions. """ target = ANSIString('|gtest|n') - result = u'\x1b[1m\x1b[32mTest\x1b[0m' - self.checker(target.capitalize(), result, u'Test') + result = '\x1b[1m\x1b[32mTest\x1b[0m' + self.checker(target.capitalize(), result, 'Test') def test_mxp_agnostic(self): """ @@ -131,7 +131,7 @@ class ANSIStringTestCase(TestCase): self.assertEqual(len(ANSIString(mxp1)), len(ANSIString(mxp1).split("\n")[0])) self.assertEqual(len(ANSIString(mxp2)), len(ANSIString(mxp2).split("\n")[0])) self.assertEqual(mxp1, ANSIString(mxp1)) - self.assertEqual(mxp2, unicode(ANSIString(mxp2))) + self.assertEqual(mxp2, str(ANSIString(mxp2))) def test_add(self): """ @@ -140,8 +140,8 @@ class ANSIStringTestCase(TestCase): a = ANSIString("|gTest") b = ANSIString("|cString|n") c = a + b - result = u'\x1b[1m\x1b[32mTest\x1b[1m\x1b[36mString\x1b[0m' - self.checker(c, result, u'TestString') + result = '\x1b[1m\x1b[32mTest\x1b[1m\x1b[36mString\x1b[0m' + self.checker(c, result, 'TestString') char_table = [9, 10, 11, 12, 22, 23, 24, 25, 26, 27] code_table = [0, 1, 2, 3, 4, 5, 6, 7, 8, 13, 14, 15, 16, 17, 18, 19, 20, 21, 28, 29, 30, 31] self.table_check(c, char_table, code_table) diff --git a/evennia/utils/text2html.py b/evennia/utils/text2html.py index 94c5587533..64a25f5742 100644 --- a/evennia/utils/text2html.py +++ b/evennia/utils/text2html.py @@ -8,7 +8,7 @@ snippet #577349 on http://code.activestate.com. (extensively modified by Griatch 2010) """ -from __future__ import absolute_import + from builtins import object import re @@ -52,7 +52,7 @@ class TextToHTMLparser(object): ('color-013', hilite + ANSI_MAGENTA), ('color-014', hilite + ANSI_CYAN), ('color-015', hilite + ANSI_WHITE) # pure white - ] + [("color-%03i" % (i + 16), XTERM256_FG % ("%i" % (i + 16))) for i in xrange(240)] + ] + [("color-%03i" % (i + 16), XTERM256_FG % ("%i" % (i + 16))) for i in range(240)] colorback = [ ('bgcolor-000', ANSI_BACK_BLACK), # pure black diff --git a/evennia/utils/txws.py b/evennia/utils/txws.py index 66be721aec..ef9a4bda8f 100644 --- a/evennia/utils/txws.py +++ b/evennia/utils/txws.py @@ -246,7 +246,7 @@ def make_hybi07_frame_dwim(buf): # TODO: eliminate magic numbers. if isinstance(buf, str): return make_hybi07_frame(buf, opcode=0x2) - elif isinstance(buf, unicode): + elif isinstance(buf, str): return make_hybi07_frame(buf.encode("utf-8"), opcode=0x1) else: raise TypeError("In binary support mode, frame data must be either str or unicode") @@ -510,7 +510,7 @@ class WebSocketProtocol(ProtocolWrapper): elif "Sec-WebSocket-Protocol" in self.headers: protocols = self.headers["Sec-WebSocket-Protocol"] - if isinstance(protocols, basestring): + if isinstance(protocols, str): protocols = [p.strip() for p in protocols.split(',')] for protocol in protocols: diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index 667f6a209b..fbd9f88fe7 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -6,7 +6,7 @@ They provide some useful string and conversion methods that might be of use when designing your own game. """ -from __future__ import division, print_function + from builtins import object, range from future.utils import viewkeys, raise_ @@ -33,7 +33,7 @@ _EVENNIA_DIR = settings.EVENNIA_DIR _GAME_DIR = settings.GAME_DIR try: - import cPickle as pickle + import pickle as pickle except ImportError: import pickle @@ -595,12 +595,12 @@ def dbref(inp, reqhash=True): """ if reqhash: - num = (int(inp.lstrip('#')) if (isinstance(inp, basestring) and + num = (int(inp.lstrip('#')) if (isinstance(inp, str) and inp.startswith("#") and inp.lstrip('#').isdigit()) else None) return num if num > 0 else None - elif isinstance(inp, basestring): + elif isinstance(inp, str): inp = inp.lstrip('#') return int(inp) if inp.isdigit() and int(inp) > 0 else None else: @@ -723,7 +723,7 @@ def to_unicode(obj, encoding='utf-8', force_string=False): """ - if force_string and not isinstance(obj, basestring): + if force_string and not isinstance(obj, str): # some sort of other object. Try to # convert it to a string representation. if hasattr(obj, '__str__'): @@ -734,14 +734,14 @@ def to_unicode(obj, encoding='utf-8', force_string=False): # last resort obj = str(obj) - if isinstance(obj, basestring) and not isinstance(obj, unicode): + if isinstance(obj, str) and not isinstance(obj, str): try: - obj = unicode(obj, encoding) + obj = str(obj, encoding) return obj except UnicodeDecodeError: for alt_encoding in ENCODINGS: try: - obj = unicode(obj, alt_encoding) + obj = str(obj, alt_encoding) return obj except UnicodeDecodeError: # if we still have an error, give up @@ -768,15 +768,15 @@ def to_str(obj, encoding='utf-8', force_string=False): conversion of objects to strings. """ - if force_string and not isinstance(obj, basestring): + if force_string and not isinstance(obj, str): # some sort of other object. Try to # convert it to a string representation. try: obj = str(obj) except Exception: - obj = unicode(obj) + obj = str(obj) - if isinstance(obj, basestring) and isinstance(obj, unicode): + if isinstance(obj, str) and isinstance(obj, str): try: obj = obj.encode(encoding) return obj @@ -872,7 +872,7 @@ def inherits_from(obj, parent): else: obj_paths = ["%s.%s" % (mod.__module__, mod.__name__) for mod in obj.__class__.mro()] - if isinstance(parent, basestring): + if isinstance(parent, str): # a given string path, for direct matching parent_path = parent elif callable(parent): @@ -1266,7 +1266,7 @@ def variable_from_module(module, variable=None, default=None): result.append(mod.__dict__.get(var, default)) else: # get all - result = [val for key, val in mod.__dict__.items() + result = [val for key, val in list(mod.__dict__.items()) if not (key.startswith("_") or ismodule(val))] if len(result) == 1: @@ -1298,7 +1298,7 @@ def string_from_module(module, variable=None, default=None): if variable: return val else: - result = [v for v in make_iter(val) if isinstance(v, basestring)] + result = [v for v in make_iter(val) if isinstance(v, str)] return result if result else default return default @@ -1635,7 +1635,7 @@ def deepsize(obj, max_depth=4): _recurse(ref, dct, depth + 1) sizedict = {} _recurse(obj, sizedict, 0) - size = getsizeof(obj) + sum([p[1] for p in sizedict.values()]) + size = getsizeof(obj) + sum([p[1] for p in list(sizedict.values())]) return size @@ -1682,7 +1682,7 @@ class lazy_property(object): _STRIP_ANSI = None -_RE_CONTROL_CHAR = re.compile('[%s]' % re.escape(''.join([unichr(c) for c in range(0, 32)]))) # + range(127,160)]))) +_RE_CONTROL_CHAR = re.compile('[%s]' % re.escape(''.join([chr(c) for c in range(0, 32)]))) # + range(127,160)]))) def strip_control_sequences(string): @@ -1741,7 +1741,7 @@ def m_len(target): """ # Would create circular import if in module root. from evennia.utils.ansi import ANSI_PARSER - if inherits_from(target, basestring) and "|lt" in target: + if inherits_from(target, str) and "|lt" in target: return len(ANSI_PARSER.strip_mxp(target)) return len(target) diff --git a/evennia/web/webclient/views.py b/evennia/web/webclient/views.py index 94ecffb0e2..1b794b6198 100644 --- a/evennia/web/webclient/views.py +++ b/evennia/web/webclient/views.py @@ -4,7 +4,7 @@ This contains a simple view for rendering the webclient page and serve it eventual static content. """ -from __future__ import print_function + from django.shortcuts import render from django.contrib.auth import login, authenticate From b80fb95662f86ee4bc96bfb95f414ba32fdff5fc Mon Sep 17 00:00:00 2001 From: Ryan Stein Date: Sun, 29 Oct 2017 20:15:06 -0400 Subject: [PATCH 002/493] Fix two unhandled Deferred errors in contrib tests. --- evennia/contrib/tests.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index cfb7d59ead..d3534d03cf 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -797,6 +797,11 @@ class TestTutorialWorldMob(EvenniaTest): from evennia.contrib.tutorial_world import objects as tutobjects +def _ignoreCancelled(err, *args, **kwargs): + # Ignore the cancelled errors that we intend to occur. + from twisted.internet.defer import CancelledError + if not issubclass(err.type, CancelledError): + err.raiseException() class TestTutorialWorldObjects(CommandTest): def test_tutorialobj(self): @@ -823,6 +828,7 @@ class TestTutorialWorldObjects(CommandTest): self.call(tutobjects.CmdLight(), "", "You light torch.", obj=light) light._burnout() if hasattr(light, "deferred"): + light.deferred.addErrback(_ignoreCancelled) light.deferred.cancel() self.assertFalse(light.pk) @@ -845,6 +851,7 @@ class TestTutorialWorldObjects(CommandTest): self.assertTrue(wall.db.exit_open) wall.reset() if hasattr(wall, "deferred"): + wall.deferred.addErrback(_ignoreCancelled) wall.deferred.cancel() wall.delete() @@ -920,7 +927,7 @@ class TestTurnBattleCmd(CommandTest): self.call(tb_basic.CmdPass(), "", "You can only do that in combat. (see: help fight)") self.call(tb_basic.CmdDisengage(), "", "You can only do that in combat. (see: help fight)") self.call(tb_basic.CmdRest(), "", "Char rests to recover HP.") - + # Test equipment commands def test_turnbattleequipcmd(self): # Start with equip module specific commands. @@ -938,7 +945,7 @@ class TestTurnBattleCmd(CommandTest): self.call(tb_equip.CmdPass(), "", "You can only do that in combat. (see: help fight)") self.call(tb_equip.CmdDisengage(), "", "You can only do that in combat. (see: help fight)") self.call(tb_equip.CmdRest(), "", "Char rests to recover HP.") - + class TestTurnBattleFunc(EvenniaTest): @@ -1018,7 +1025,7 @@ class TestTurnBattleFunc(EvenniaTest): self.assertTrue(turnhandler.db.fighters == [joiner, attacker, defender]) # Remove the script at the end turnhandler.stop() - + # Test the combat functions in tb_equip too. They work mostly the same. def test_turnbattlefunc(self): attacker = create_object(tb_equip.TBEquipCharacter, key="Attacker") From bb15fed78407652bd5d757c3bbad90c851a79865 Mon Sep 17 00:00:00 2001 From: Ryan Stein Date: Sun, 29 Oct 2017 21:29:09 -0400 Subject: [PATCH 003/493] Switch to autobahn-python for WebSockets support. --- evennia/server/portal/portal.py | 8 +- evennia/server/portal/webclient.py | 83 ++-- evennia/utils/txws.py | 663 ----------------------------- requirements.txt | 1 + win_requirements.txt | 1 + 5 files changed, 42 insertions(+), 714 deletions(-) delete mode 100644 evennia/utils/txws.py diff --git a/evennia/server/portal/portal.py b/evennia/server/portal/portal.py index 75cb77e837..9a3423bc1b 100644 --- a/evennia/server/portal/portal.py +++ b/evennia/server/portal/portal.py @@ -295,25 +295,25 @@ if WEBSERVER_ENABLED: ajax_webclient = webclient_ajax.AjaxWebClient() ajax_webclient.sessionhandler = PORTAL_SESSIONS - web_root.putChild("webclientdata", ajax_webclient) + web_root.putChild(b"webclientdata", ajax_webclient) webclientstr = "\n + webclient (ajax only)" if WEBSOCKET_CLIENT_ENABLED and not websocket_started: # start websocket client port for the webclient # we only support one websocket client from evennia.server.portal import webclient - from evennia.utils.txws import WebSocketFactory + from autobahn.twisted.websocket import WebSocketServerFactory w_interface = WEBSOCKET_CLIENT_INTERFACE w_ifacestr = '' if w_interface not in ('0.0.0.0', '::') or len(WEBSERVER_INTERFACES) > 1: w_ifacestr = "-%s" % interface port = WEBSOCKET_CLIENT_PORT - factory = protocol.ServerFactory() + factory = WebSocketServerFactory() factory.noisy = False factory.protocol = webclient.WebSocketClient factory.sessionhandler = PORTAL_SESSIONS - websocket_service = internet.TCPServer(port, WebSocketFactory(factory), interface=w_interface) + websocket_service = internet.TCPServer(port, factory, interface=w_interface) websocket_service.setName('EvenniaWebSocket%s:%s' % (w_ifacestr, proxyport)) PORTAL.services.addService(websocket_service) websocket_started = True diff --git a/evennia/server/portal/webclient.py b/evennia/server/portal/webclient.py index d025b85018..6eaf66e1aa 100644 --- a/evennia/server/portal/webclient.py +++ b/evennia/server/portal/webclient.py @@ -2,8 +2,8 @@ Webclient based on websockets. This implements a webclient with WebSockets (http://en.wikipedia.org/wiki/WebSocket) -by use of the txws implementation (https://github.com/MostAwesomeDude/txWS). It is -used together with evennia/web/media/javascript/evennia_websocket_webclient.js. +by use of the autobahn-python package's implementation (https://github.com/crossbario/autobahn-python). +It is used together with evennia/web/media/javascript/evennia_websocket_webclient.js. All data coming into the webclient is in the form of valid JSON on the form @@ -22,26 +22,17 @@ from evennia.server.session import Session from evennia.utils.utils import to_str, mod_import from evennia.utils.ansi import parse_ansi from evennia.utils.text2html import parse_html +from autobahn.twisted.websocket import WebSocketServerProtocol _RE_SCREENREADER_REGEX = re.compile(r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE) _CLIENT_SESSIONS = mod_import(settings.SESSION_ENGINE).SessionStore -class WebSocketClient(Protocol, Session): +class WebSocketClient(WebSocketServerProtocol, Session): """ Implements the server-side of the Websocket connection. """ - def connectionMade(self): - """ - This is called when the connection is first established. - - """ - self.transport.validationMade = self.validationMade - client_address = self.transport.client - client_address = client_address[0] if client_address else None - self.init_session("websocket", client_address, self.factory.sessionhandler) - def get_client_session(self): """ Get the Client browser session (used for auto-login based on browser session) @@ -52,7 +43,7 @@ class WebSocketClient(Protocol, Session): """ try: - self.csessid = self.transport.location.split("?", 1)[1] + self.csessid = self.http_request_uri.split("?", 1)[1] except IndexError: # this may happen for custom webclients not caring for the # browser session. @@ -61,12 +52,15 @@ class WebSocketClient(Protocol, Session): if self.csessid: return _CLIENT_SESSIONS(session_key=self.csessid) - def validationMade(self): + def onOpen(self): """ - This is called from the (modified) txws websocket library when - the ws handshake and validation has completed fully. + This is called when the WebSocket connection is fully established. """ + client_address = self.transport.client + client_address = client_address[0] if client_address else None + self.init_session("websocket", client_address, self.factory.sessionhandler) + csession = self.get_client_session() uid = csession and csession.get("webclient_authenticated_uid", None) if uid: @@ -85,43 +79,48 @@ class WebSocketClient(Protocol, Session): disconnect this protocol. Args: - reason (str): Motivation for the disconnection. + reason (str or None): Motivation for the disconnection. """ - self.data_out(text=((reason or "",), {})) + # autobahn-python: 1000 for a normal close, 3000-4999 for app. specific, + # in case anyone wants to expose this functionality later. + # + # sendClose() under autobahn/websocket/interfaces.py + self.sendClose(1000, reason) - csession = self.get_client_session() - - if csession: - csession["webclient_authenticated_uid"] = None - csession.save() - self.logged_in = False - self.connectionLost(reason) - - def connectionLost(self, reason): + def onClose(self, wasClean, code=None, reason=None): """ This is executed when the connection is lost for whatever reason. it can also be called directly, from the disconnect method. Args: + wasClean (bool): ``True`` if the WebSocket was closed cleanly. reason (str): Motivation for the lost connection. + code (int or None): Close status as sent by the WebSocket peer. + reason (str or None): Close reason as sent by the WebSocket peer. """ - print("In connectionLost of webclient") + csession = self.get_client_session() + + if csession: + csession["webclient_authenticated_uid"] = None + csession.save() + self.logged_in = False + self.sessionhandler.disconnect(self) - self.transport.close() - def dataReceived(self, string): + def onMessage(self, payload, isBinary): """ - Method called when data is coming in over the websocket - connection. This is always a JSON object on the following - form: - [cmdname, [args], {kwargs}] + Callback fired when a complete WebSocket message was received. + Args: + payload (bytes): The WebSocket message received. + isBinary (bool): Flag indicating whether payload is binary or + UTF-8 encoded text. """ - cmdarray = json.loads(string) + cmdarray = json.loads(payload) if cmdarray: self.data_in(**{cmdarray[0]: [cmdarray[1], cmdarray[2]]}) @@ -133,7 +132,7 @@ class WebSocketClient(Protocol, Session): line (str): Text to send. """ - return self.transport.write(line) + return self.sendMessage(line.encode()) def at_login(self): csession = self.get_client_session() @@ -162,22 +161,12 @@ class WebSocketClient(Protocol, Session): this point. """ - if "websocket_close" in kwargs: self.disconnect() return self.sessionhandler.data_in(self, **kwargs) - def data_out(self, **kwargs): - """ - Data Evennia->User. - - Kwargs: - kwargs (any): Options ot the protocol - """ - self.sessionhandler.data_out(self, **kwargs) - def send_text(self, *args, **kwargs): """ Send text data. This will pre-process the text for diff --git a/evennia/utils/txws.py b/evennia/utils/txws.py deleted file mode 100644 index ef9a4bda8f..0000000000 --- a/evennia/utils/txws.py +++ /dev/null @@ -1,663 +0,0 @@ -# Copyright (c) 2011 Oregon State University Open Source Lab -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to -# deal in the Software without restriction, including without limitation the -# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -# sell copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included -# in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -# NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -# USE OR OTHER DEALINGS IN THE SOFTWARE. - -""" -Blind reimplementation of WebSockets as a standalone wrapper for Twisted -protocols. -""" -from builtins import range - -__version__ = "0.7.1" - -from base64 import b64encode, b64decode -from hashlib import md5, sha1 -from string import digits -from struct import pack, unpack - -from twisted.internet.interfaces import ISSLTransport -from twisted.protocols.policies import ProtocolWrapper, WrappingFactory -from twisted.python import log -from twisted.web.http import datetimeToString - - -class WSException(Exception): - """ - Something stupid happened here. - - If this class escapes txWS, then something stupid happened in multiple - places. - """ - -# Flavors of WS supported here. -# HYBI00 - Hixie-76, HyBi-00. Challenge/response after headers, very minimal -# framing. Tricky to start up, but very smooth sailing afterwards. -# HYBI07 - HyBi-07. Modern "standard" handshake. Bizarre masked frames, lots -# of binary data packing. -# HYBI10 - HyBi-10. Just like HyBi-07. No, seriously. *Exactly* the same, -# except for the protocol number. -# RFC6455 - RFC 6455. The official WebSocket protocol standard. The protocol -# number is 13, but otherwise it is identical to HyBi-07. - - -HYBI00, HYBI07, HYBI10, RFC6455 = list(range(4)) - -# States of the state machine. Because there are no reliable byte counts for -# any of this, we don't use StatefulProtocol; instead, we use custom state -# enumerations. Yay! - -REQUEST, NEGOTIATING, CHALLENGE, FRAMES = list(range(4)) - -# Control frame specifiers. Some versions of WS have control signals sent -# in-band. Adorable, right? - -NORMAL, CLOSE, PING, PONG = list(range(4)) - -opcode_types = { - 0x0: NORMAL, - 0x1: NORMAL, - 0x2: NORMAL, - 0x8: CLOSE, - 0x9: PING, - 0xa: PONG, -} - -encoders = { - "base64": b64encode, -} - -decoders = { - "base64": b64decode, -} - -# Fake HTTP stuff, and a couple convenience methods for examining fake HTTP -# headers. - - -def http_headers(s): - """ - Create a dictionary of data from raw HTTP headers. - """ - - d = {} - - for line in s.split("\r\n"): - try: - key, value = [i.strip() for i in line.split(":", 1)] - d[key] = value - except ValueError: - # malformed header, skip it - pass - - return d - - -def is_websocket(headers): - """ - Determine whether a given set of headers is asking for WebSockets. - """ - - return ("upgrade" in headers.get("Connection", "").lower() and - headers.get("Upgrade").lower() == "websocket") - - -def is_hybi00(headers): - """ - Determine whether a given set of headers is HyBi-00-compliant. - - Hixie-76 and HyBi-00 use a pair of keys in the headers to handshake with - servers. - """ - - return "Sec-WebSocket-Key1" in headers and "Sec-WebSocket-Key2" in headers - -# Authentication for WS. - - -def complete_hybi00(headers, challenge): - """ - Generate the response for a HyBi-00 challenge. - """ - - key1 = headers["Sec-WebSocket-Key1"] - key2 = headers["Sec-WebSocket-Key2"] - - first = int("".join(i for i in key1 if i in digits)) // key1.count(" ") - second = int("".join(i for i in key2 if i in digits)) // key2.count(" ") - - nonce = pack(">II8s", first, second, challenge) - - return md5(nonce).digest() - - -def make_accept(key): - """ - Create an "accept" response for a given key. - - This dance is expected to somehow magically make WebSockets secure. - """ - - guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" - - return sha1("%s%s" % (key, guid)).digest().encode("base64").strip() - -# Frame helpers. -# Separated out to make unit testing a lot easier. -# Frames are bonghits in newer WS versions, so helpers are appreciated. - - -def make_hybi00_frame(buf): - """ - Make a HyBi-00 frame from some data. - - This function does exactly zero checks to make sure that the data is safe - and valid text without any 0xff bytes. - """ - - return "\x00%s\xff" % buf - - -def parse_hybi00_frames(buf): - """ - Parse HyBi-00 frames, returning unwrapped frames and any unmatched data. - - This function does not care about garbage data on the wire between frames, - and will actively ignore it. - """ - - start = buf.find("\x00") - tail = 0 - frames = [] - - while start != -1: - end = buf.find("\xff", start + 1) - if end == -1: - # Incomplete frame, try again later. - break - else: - # Found a frame, put it in the list. - frame = buf[start + 1:end] - frames.append((NORMAL, frame)) - tail = end + 1 - start = buf.find("\x00", end + 1) - - # Adjust the buffer and return. - buf = buf[tail:] - return frames, buf - - -def mask(buf, key): - """ - Mask or unmask a buffer of bytes with a masking key. - - The key must be exactly four bytes long. - """ - - # This is super-secure, I promise~ - key = [ord(i) for i in key] - buf = list(buf) - for i, char in enumerate(buf): - buf[i] = chr(ord(char) ^ key[i % 4]) - return "".join(buf) - - -def make_hybi07_frame(buf, opcode=0x1): - """ - Make a HyBi-07 frame. - - This function always creates unmasked frames, and attempts to use the - smallest possible lengths. - """ - - if len(buf) > 0xffff: - length = "\x7f%s" % pack(">Q", len(buf)) - elif len(buf) > 0x7d: - length = "\x7e%s" % pack(">H", len(buf)) - else: - length = chr(len(buf)) - - # Always make a normal packet. - header = chr(0x80 | opcode) - frame = "%s%s%s" % (header, length, buf) - return frame - - -def make_hybi07_frame_dwim(buf): - """ - Make a HyBi-07 frame with binary or text data according to the type of buf. - """ - - # TODO: eliminate magic numbers. - if isinstance(buf, str): - return make_hybi07_frame(buf, opcode=0x2) - elif isinstance(buf, str): - return make_hybi07_frame(buf.encode("utf-8"), opcode=0x1) - else: - raise TypeError("In binary support mode, frame data must be either str or unicode") - - -def parse_hybi07_frames(buf): - """ - Parse HyBi-07 frames in a highly compliant manner. - """ - - start = 0 - frames = [] - - while True: - # If there's not at least two bytes in the buffer, bail. - if len(buf) - start < 2: - break - - # Grab the header. This single byte holds some flags nobody cares - # about, and an opcode which nobody cares about. - header = ord(buf[start]) - if header & 0x70: - # At least one of the reserved flags is set. Pork chop sandwiches! - raise WSException("Reserved flag in HyBi-07 frame (%d)" % header) - #frames.append(("", CLOSE)) - # return frames, buf - - # Get the opcode, and translate it to a local enum which we actually - # care about. - opcode = header & 0xf - try: - opcode = opcode_types[opcode] - except KeyError: - raise WSException("Unknown opcode %d in HyBi-07 frame" % opcode) - - # Get the payload length and determine whether we need to look for an - # extra length. - length = ord(buf[start + 1]) - masked = length & 0x80 - length &= 0x7f - - # The offset we're gonna be using to walk through the frame. We use - # this because the offset is variable depending on the length and - # mask. - offset = 2 - - # Extra length fields. - if length == 0x7e: - if len(buf) - start < 4: - break - - length = buf[start + 2:start + 4] - length = unpack(">H", length)[0] - offset += 2 - elif length == 0x7f: - if len(buf) - start < 10: - break - - # Protocol bug: The top bit of this long long *must* be cleared; - # that is, it is expected to be interpreted as signed. That's - # fucking stupid, if you don't mind me saying so, and so we're - # interpreting it as unsigned anyway. If you wanna send exabytes - # of data down the wire, then go ahead! - length = buf[start + 2:start + 10] - length = unpack(">Q", length)[0] - offset += 8 - - if masked: - if len(buf) - (start + offset) < 4: - break - - key = buf[start + offset:start + offset + 4] - offset += 4 - - if len(buf) - (start + offset) < length: - break - - data = buf[start + offset:start + offset + length] - - if masked: - data = mask(data, key) - - if opcode == CLOSE: - if len(data) >= 2: - # Gotta unpack the opcode and return usable data here. - data = unpack(">H", data[:2])[0], data[2:] - else: - # No reason given; use generic data. - data = 1000, "No reason given" - - frames.append((opcode, data)) - start += offset + length - - return frames, buf[start:] - - -class WebSocketProtocol(ProtocolWrapper): - """ - Protocol which wraps another protocol to provide a WebSockets transport - layer. - """ - - buf = "" - codec = None - location = "/" - host = "example.com" - origin = "http://example.com" - state = REQUEST - flavor = None - do_binary_frames = False - - def __init__(self, *args, **kwargs): - ProtocolWrapper.__init__(self, *args, **kwargs) - self.pending_frames = [] - - def setBinaryMode(self, mode): - """ - If True, send str as binary and unicode as text. - - Defaults to false for backwards compatibility. - """ - self.do_binary_frames = bool(mode) - - def isSecure(self): - """ - Borrowed technique for determining whether this connection is over - SSL/TLS. - """ - - return ISSLTransport(self.transport, None) is not None - - def sendCommonPreamble(self): - """ - Send the preamble common to all WebSockets connections. - - This might go away in the future if WebSockets continue to diverge. - """ - - self.transport.writeSequence([ - "HTTP/1.1 101 FYI I am not a webserver\r\n", - "Server: TwistedWebSocketWrapper/1.0\r\n", - "Date: %s\r\n" % datetimeToString(), - "Upgrade: WebSocket\r\n", - "Connection: Upgrade\r\n", - ]) - - def sendHyBi00Preamble(self): - """ - Send a HyBi-00 preamble. - """ - - protocol = "wss" if self.isSecure() else "ws" - - self.sendCommonPreamble() - - self.transport.writeSequence([ - "Sec-WebSocket-Origin: %s\r\n" % self.origin, - "Sec-WebSocket-Location: %s://%s%s\r\n" % (protocol, self.host, - self.location), - "WebSocket-Protocol: %s\r\n" % self.codec, - "Sec-WebSocket-Protocol: %s\r\n" % self.codec, - "\r\n", - ]) - - def sendHyBi07Preamble(self): - """ - Send a HyBi-07 preamble. - """ - - self.sendCommonPreamble() - challenge = self.headers["Sec-WebSocket-Key"] - response = make_accept(challenge) - - self.transport.write("Sec-WebSocket-Accept: %s\r\n\r\n" % response) - - def parseFrames(self): - """ - Find frames in incoming data and pass them to the underlying protocol. - """ - - if self.flavor == HYBI00: - parser = parse_hybi00_frames - elif self.flavor in (HYBI07, HYBI10, RFC6455): - parser = parse_hybi07_frames - else: - raise WSException("Unknown flavor %r" % self.flavor) - - try: - frames, self.buf = parser(self.buf) - except WSException as wse: - # Couldn't parse all the frames, something went wrong, let's bail. - self.close(wse.args[0]) - return - - for frame in frames: - opcode, data = frame - if opcode == NORMAL: - # Business as usual. Decode the frame, if we have a decoder. - if self.codec: - data = decoders[self.codec](data) - # Pass the frame to the underlying protocol. - ProtocolWrapper.dataReceived(self, data) - elif opcode == CLOSE: - # The other side wants us to close. I wonder why? - reason, text = data - log.msg("Closing connection: %r (%d)" % (text, reason)) - - # Close the connection. - self.close() - - def sendFrames(self): - """ - Send all pending frames. - """ - - if self.state != FRAMES: - return - - if self.flavor == HYBI00: - maker = make_hybi00_frame - elif self.flavor in (HYBI07, HYBI10, RFC6455): - if self.do_binary_frames: - maker = make_hybi07_frame_dwim - else: - maker = make_hybi07_frame - else: - raise WSException("Unknown flavor %r" % self.flavor) - - for frame in self.pending_frames: - # Encode the frame before sending it. - if self.codec: - frame = encoders[self.codec](frame) - packet = maker(frame) - self.transport.write(packet) - self.pending_frames = [] - - def validateHeaders(self): - """ - Check received headers for sanity and correctness, and stash any data - from them which will be required later. - """ - - # Obvious but necessary. - if not is_websocket(self.headers): - log.msg("Not handling non-WS request") - return False - - # Stash host and origin for those browsers that care about it. - if "Host" in self.headers: - self.host = self.headers["Host"] - if "Origin" in self.headers: - self.origin = self.headers["Origin"] - - # Check whether a codec is needed. WS calls this a "protocol" for - # reasons I cannot fathom. Newer versions of noVNC (0.4+) sets - # multiple comma-separated codecs, handle this by chosing first one - # we can encode/decode. - protocols = None - if "WebSocket-Protocol" in self.headers: - protocols = self.headers["WebSocket-Protocol"] - elif "Sec-WebSocket-Protocol" in self.headers: - protocols = self.headers["Sec-WebSocket-Protocol"] - - if isinstance(protocols, str): - protocols = [p.strip() for p in protocols.split(',')] - - for protocol in protocols: - if protocol in encoders or protocol in decoders: - log.msg("Using WS protocol %s!" % protocol) - self.codec = protocol - break - - log.msg("Couldn't handle WS protocol %s!" % protocol) - - if not self.codec: - return False - - # Start the next phase of the handshake for HyBi-00. - if is_hybi00(self.headers): - log.msg("Starting HyBi-00/Hixie-76 handshake") - self.flavor = HYBI00 - self.state = CHALLENGE - - # Start the next phase of the handshake for HyBi-07+. - if "Sec-WebSocket-Version" in self.headers: - version = self.headers["Sec-WebSocket-Version"] - if version == "7": - log.msg("Starting HyBi-07 conversation") - self.sendHyBi07Preamble() - self.flavor = HYBI07 - self.state = FRAMES - elif version == "8": - log.msg("Starting HyBi-10 conversation") - self.sendHyBi07Preamble() - self.flavor = HYBI10 - self.state = FRAMES - elif version == "13": - log.msg("Starting RFC 6455 conversation") - self.sendHyBi07Preamble() - self.flavor = RFC6455 - self.state = FRAMES - else: - log.msg("Can't support protocol version %s!" % version) - return False - - self.validationMade() # custom Evennia addition - return True - - def dataReceived(self, data): - self.buf += data - - oldstate = None - - while oldstate != self.state: - oldstate = self.state - - # Handle initial requests. These look very much like HTTP - # requests, but aren't. We need to capture the request path for - # those browsers which want us to echo it back to them (Chrome, - # mainly.) - # These lines look like: - # GET /some/path/to/a/websocket/resource HTTP/1.1 - if self.state == REQUEST: - if "\r\n" in self.buf: - request, chaff, self.buf = self.buf.partition("\r\n") - try: - # verb and version are never used, maybe in the future. - #verb, self.location, version - _, self.location, _ = request.split(" ") - except ValueError: - self.loseConnection() - else: - self.state = NEGOTIATING - - elif self.state == NEGOTIATING: - # Check to see if we've got a complete set of headers yet. - if "\r\n\r\n" in self.buf: - head, chaff, self.buf = self.buf.partition("\r\n\r\n") - self.headers = http_headers(head) - # Validate headers. This will cause a state change. - if not self.validateHeaders(): - self.loseConnection() - - elif self.state == CHALLENGE: - # Handle the challenge. This is completely exclusive to - # HyBi-00/Hixie-76. - if len(self.buf) >= 8: - challenge, self.buf = self.buf[:8], self.buf[8:] - response = complete_hybi00(self.headers, challenge) - self.sendHyBi00Preamble() - self.transport.write(response) - log.msg("Completed HyBi-00/Hixie-76 handshake") - # We're all finished here; start sending frames. - self.state = FRAMES - - elif self.state == FRAMES: - self.parseFrames() - - # Kick any pending frames. This is needed because frames might have - # started piling up early; we can get write()s from our protocol above - # when they makeConnection() immediately, before our browser client - # actually sends any data. In those cases, we need to manually kick - # pending frames. - if self.pending_frames: - self.sendFrames() - - def write(self, data): - """ - Write to the transport. - - This method will only be called by the underlying protocol. - """ - - self.pending_frames.append(data) - self.sendFrames() - - def writeSequence(self, data): - """ - Write a sequence of data to the transport. - - This method will only be called by the underlying protocol. - """ - - self.pending_frames.extend(data) - self.sendFrames() - - def close(self, reason=""): - """ - Close the connection. - - This includes telling the other side we're closing the connection. - - If the other side didn't signal that the connection is being closed, - then we might not see their last message, but since their last message - should, according to the spec, be a simple acknowledgement, it - shouldn't be a problem. - """ - - # Send a closing frame. It's only polite. (And might keep the browser - # from hanging.) - if self.flavor in (HYBI07, HYBI10, RFC6455): - frame = make_hybi07_frame(reason, opcode=0x8) - self.transport.write(frame) - - self.loseConnection() - - -class WebSocketFactory(WrappingFactory): - """ - Factory which wraps another factory to provide WebSockets transports for - all of its protocols. - """ - noisy = False - protocol = WebSocketProtocol diff --git a/requirements.txt b/requirements.txt index be3cf558e5..7ce920f3ce 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,4 @@ pillow == 2.9.0 pytz future >= 0.15.2 django-sekizai +autobahn >= 17.9.3 diff --git a/win_requirements.txt b/win_requirements.txt index 7012643657..a89abc615b 100644 --- a/win_requirements.txt +++ b/win_requirements.txt @@ -10,3 +10,4 @@ pillow == 2.9.0 pytz future >= 0.15.2 django-sekizai +autobahn >= 17.9.3 From 8c15dff56db1d678ac28c3a2c658bf32403ae26f Mon Sep 17 00:00:00 2001 From: Ryan Stein Date: Sun, 29 Oct 2017 21:36:13 -0400 Subject: [PATCH 004/493] Update random_string_generator's use of sre_parse.parse().data for Py3. --- evennia/contrib/random_string_generator.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/evennia/contrib/random_string_generator.py b/evennia/contrib/random_string_generator.py index dcce163b70..a6ceb805fc 100644 --- a/evennia/contrib/random_string_generator.py +++ b/evennia/contrib/random_string_generator.py @@ -185,8 +185,8 @@ class RandomStringGenerator(object): tree = re.sre_parse.parse(regex).data # `tree` contains a list of elements in the regular expression for element in tree: - # `eleemnt` is also a list, the first element is a string - name = element[0] + # `element` is also a list, the first element is a string + name = str(element[0]).lower() desc = {"min": 1, "max": 1} # If `.`, break here @@ -213,10 +213,11 @@ class RandomStringGenerator(object): def _find_literal(self, element): """Find the literal corresponding to a piece of regular expression.""" + name = str(element[0]).lower() chars = [] - if element[0] == "literal": + if name == "literal": chars.append(chr(element[1])) - elif element[0] == "in": + elif name == "in": negate = False if element[1][0][0] == "negate": negate = True @@ -233,10 +234,10 @@ class RandomStringGenerator(object): chars.remove(char) else: chars.append(char) - elif element[0] == "range": + elif name == "range": chars = [chr(i) for i in range(element[1][0], element[1][1] + 1)] - elif element[0] == "category": - category = element[1] + elif name == "category": + category = str(element[1]).lower() if category == "category_digit": chars = list(string.digits) elif category == "category_word": From c5c44f3e0c5efaa8ffa3c6db99f1edd7a80f1991 Mon Sep 17 00:00:00 2001 From: Ryan Stein Date: Sun, 29 Oct 2017 21:37:12 -0400 Subject: [PATCH 005/493] Update contrib.mapbuilder for Py3. --- evennia/contrib/mapbuilder.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/evennia/contrib/mapbuilder.py b/evennia/contrib/mapbuilder.py index 3bbf5c72d1..7d9b07d136 100644 --- a/evennia/contrib/mapbuilder.py +++ b/evennia/contrib/mapbuilder.py @@ -276,7 +276,7 @@ COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS) # Helper function for readability. def _map_to_list(game_map): """ - Splits multi line map string into list of rows, treats for UTF-8 encoding. + Splits multi line map string into list of rows. Args: game_map (str): An ASCII map @@ -285,9 +285,7 @@ def _map_to_list(game_map): list (list): The map split into rows """ - list_map = game_map.split('\n') - return [character.decode('UTF-8') if isinstance(character, str) - else character for character in list_map] + return game_map.split('\n') def build_map(caller, game_map, legend, iterations=1, build_exits=True): From 8dc51b9fb4e5f81900796d4549def9c65b2e65b0 Mon Sep 17 00:00:00 2001 From: Ryan Stein Date: Sun, 29 Oct 2017 21:38:16 -0400 Subject: [PATCH 006/493] Fix revision rendering and make use of ascii_letters. --- evennia/__init__.py | 11 ++++------- evennia/server/evennia_launcher.py | 4 ++-- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/evennia/__init__.py b/evennia/__init__.py index 92026cb1ec..682749ab5a 100644 --- a/evennia/__init__.py +++ b/evennia/__init__.py @@ -19,7 +19,6 @@ See www.evennia.com for full documentation. """ -from builtins import object # Delayed loading of properties @@ -104,7 +103,10 @@ def _create_version(): except IOError as err: print(err) try: - version = "%s (rev %s)" % (version, check_output("git rev-parse --short HEAD", shell=True, cwd=root, stderr=STDOUT).strip()) + rev = check_output( + "git rev-parse --short HEAD", + shell=True, cwd=root, stderr=STDOUT).strip().decode() + version = "%s (rev %s)" % (version, rev) except (IOError, CalledProcessError): # ignore if we cannot get to git pass @@ -314,8 +316,3 @@ def _init(): syscmdkeys = SystemCmds() del SystemCmds del _EvContainer - - -del object -del absolute_import -del print_function diff --git a/evennia/server/evennia_launcher.py b/evennia/server/evennia_launcher.py index 559b9358bf..f96001fc06 100644 --- a/evennia/server/evennia_launcher.py +++ b/evennia/server/evennia_launcher.py @@ -412,7 +412,7 @@ def evennia_version(): try: rev = check_output( "git rev-parse --short HEAD", - shell=True, cwd=EVENNIA_ROOT, stderr=STDOUT).strip() + shell=True, cwd=EVENNIA_ROOT, stderr=STDOUT).strip().decode() version = "%s (rev %s)" % (version, rev) except (IOError, CalledProcessError): # move on if git is not answering @@ -502,7 +502,7 @@ def create_secret_key(): """ import random import string - secret_key = list((string.letters + + secret_key = list((string.ascii_letters + string.digits + string.punctuation).replace("\\", "") .replace("'", '"').replace("{", "_").replace("}", "-")) random.shuffle(secret_key) From 1da3e0caa070d0498c130008ace4f7c8bb36bfa5 Mon Sep 17 00:00:00 2001 From: Ryan Stein Date: Sun, 29 Oct 2017 21:39:24 -0400 Subject: [PATCH 007/493] zope.interface.implements() is deprecated. Use implementer decorator. --- evennia/contrib/egi_client/client.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/evennia/contrib/egi_client/client.py b/evennia/contrib/egi_client/client.py index c3cb902801..c7490bf0b3 100644 --- a/evennia/contrib/egi_client/client.py +++ b/evennia/contrib/egi_client/client.py @@ -11,7 +11,7 @@ from twisted.internet.defer import inlineCallbacks from twisted.web.client import Agent, _HTTP11ClientFactory, HTTPConnectionPool from twisted.web.http_headers import Headers from twisted.web.iweb import IBodyProducer -from zope.interface import implements +from zope.interface import implementer from evennia.accounts.models import AccountDB from evennia.server.sessionhandler import SESSIONS @@ -144,12 +144,11 @@ class SimpleResponseReceiver(protocol.Protocol): def connectionLost(self, reason=protocol.connectionDone): self.d.callback((self.status_code, self.buf)) - +@implementer(IBodyProducer) class StringProducer(object): """ Used for feeding a request body to the tx HTTP client. """ - implements(IBodyProducer) def __init__(self, body): self.body = body From b88c74a3169815d8f80eab02886a0977dc080d3d Mon Sep 17 00:00:00 2001 From: Ryan Stein Date: Sun, 29 Oct 2017 21:43:32 -0400 Subject: [PATCH 008/493] Convert the telnet protocols over for Py3. --- evennia/server/portal/mccp.py | 4 +- evennia/server/portal/mssp.py | 6 +-- evennia/server/portal/mxp.py | 6 ++- evennia/server/portal/naws.py | 9 ++-- evennia/server/portal/suppress_ga.py | 2 +- evennia/server/portal/telnet.py | 20 +++++---- evennia/server/portal/telnet_oob.py | 63 +++++++++++++++------------- evennia/server/portal/ttype.py | 8 ++-- 8 files changed, 63 insertions(+), 55 deletions(-) diff --git a/evennia/server/portal/mccp.py b/evennia/server/portal/mccp.py index b7e87206c0..7890c5cf8f 100644 --- a/evennia/server/portal/mccp.py +++ b/evennia/server/portal/mccp.py @@ -18,7 +18,7 @@ from builtins import object import zlib # negotiations for v1 and v2 of the protocol -MCCP = chr(86) +MCCP = b'\x56' FLUSH = zlib.Z_SYNC_FLUSH @@ -85,6 +85,6 @@ class Mccp(object): """ self.protocol.protocol_flags['MCCP'] = True - self.protocol.requestNegotiation(MCCP, '') + self.protocol.requestNegotiation(MCCP, b'') self.protocol.zlib = zlib.compressobj(9) self.protocol.handshake_done() diff --git a/evennia/server/portal/mssp.py b/evennia/server/portal/mssp.py index 29a40ca285..254d1b92fe 100644 --- a/evennia/server/portal/mssp.py +++ b/evennia/server/portal/mssp.py @@ -14,9 +14,9 @@ from builtins import object from django.conf import settings from evennia.utils import utils -MSSP = chr(70) -MSSP_VAR = chr(1) -MSSP_VAL = chr(2) +MSSP = b'\x46' +MSSP_VAR = b'\x01' +MSSP_VAL = b'\x02' # try to get the customized mssp info, if it exists. diff --git a/evennia/server/portal/mxp.py b/evennia/server/portal/mxp.py index 44bc9628ea..30c3ce9949 100644 --- a/evennia/server/portal/mxp.py +++ b/evennia/server/portal/mxp.py @@ -18,7 +18,9 @@ import re LINKS_SUB = re.compile(r'\|lc(.*?)\|lt(.*?)\|le', re.DOTALL) -MXP = chr(91) +# MXP Telnet option +MXP = b'\x5b' + MXP_TEMPSECURE = "\x1B[4z" MXP_SEND = MXP_TEMPSECURE + \ "" + \ @@ -84,5 +86,5 @@ class Mxp(object): """ self.protocol.protocol_flags["MXP"] = True - self.protocol.requestNegotiation(MXP, '') + self.protocol.requestNegotiation(MXP, b'') self.protocol.handshake_done() diff --git a/evennia/server/portal/naws.py b/evennia/server/portal/naws.py index 13f08c7f9a..0663231d6b 100644 --- a/evennia/server/portal/naws.py +++ b/evennia/server/portal/naws.py @@ -9,11 +9,12 @@ NAWS allows telnet clients to report their current window size to the client and update it when the size changes """ +from codecs import encode as codecs_encode from builtins import object from django.conf import settings -NAWS = chr(31) -IS = chr(0) +NAWS = b'\x1f' +IS = b'\x00' # default taken from telnet specification DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH DEFAULT_HEIGHT = settings.CLIENT_DEFAULT_HEIGHT @@ -76,6 +77,6 @@ class Naws(object): if len(options) == 4: # NAWS is negotiated with 16bit words width = options[0] + options[1] - self.protocol.protocol_flags['SCREENWIDTH'][0] = int(width.encode('hex'), 16) + self.protocol.protocol_flags['SCREENWIDTH'][0] = int(codecs_encode(width, 'hex'), 16) height = options[2] + options[3] - self.protocol.protocol_flags['SCREENHEIGHT'][0] = int(height.encode('hex'), 16) + self.protocol.protocol_flags['SCREENHEIGHT'][0] = int(codecs_encode(height, 'hex'), 16) diff --git a/evennia/server/portal/suppress_ga.py b/evennia/server/portal/suppress_ga.py index c13fea62ba..723646b520 100644 --- a/evennia/server/portal/suppress_ga.py +++ b/evennia/server/portal/suppress_ga.py @@ -14,7 +14,7 @@ http://www.faqs.org/rfcs/rfc858.html """ from builtins import object -SUPPRESS_GA = chr(3) +SUPPRESS_GA = b'\x03' # default taken from telnet specification diff --git a/evennia/server/portal/telnet.py b/evennia/server/portal/telnet.py index 4112d85e2a..ec41bbfc8c 100644 --- a/evennia/server/portal/telnet.py +++ b/evennia/server/portal/telnet.py @@ -20,10 +20,10 @@ from evennia.utils import ansi from evennia.utils.utils import to_str _RE_N = re.compile(r"\|n$") -_RE_LEND = re.compile(r"\n$|\r$|\r\n$|\r\x00$|", re.MULTILINE) -_RE_LINEBREAK = re.compile(r"\n\r|\r\n|\n|\r", re.DOTALL + re.MULTILINE) +_RE_LEND = re.compile(br"\n$|\r$|\r\n$|\r\x00$|", re.MULTILINE) +_RE_LINEBREAK = re.compile(br"\n\r|\r\n|\n|\r", re.DOTALL + re.MULTILINE) _RE_SCREENREADER_REGEX = re.compile(r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE) -_IDLE_COMMAND = settings.IDLE_COMMAND + "\n" +_IDLE_COMMAND = str.encode(settings.IDLE_COMMAND + "\n") class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): @@ -43,7 +43,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): """ # initialize the session - self.line_buffer = "" + self.line_buffer = b"" client_address = self.transport.client client_address = client_address[0] if client_address else None # this number is counted down for every handshake that completes. @@ -208,18 +208,18 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): if self.line_buffer and len(data) > 1: # buffer exists, it is terminated by the first line feed data[0] = self.line_buffer + data[0] - self.line_buffer = "" + self.line_buffer = b"" # if the last data split is empty, it means all splits have # line breaks, if not, it is unterminated and must be # buffered. self.line_buffer += data.pop() # send all data chunks for dat in data: - self.data_in(text=dat + "\n") + self.data_in(text=dat + b"\n") def _write(self, data): """hook overloading the one used in plain telnet""" - data = data.replace('\n', '\r\n').replace('\r\r\n', '\r\n') + data = data.replace(b'\n', b'\r\n').replace(b'\r\r\n', b'\r\n') super(TelnetProtocol, self)._write(mccp_compress(self, data)) def sendLine(self, line): @@ -231,8 +231,9 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): """ # escape IAC in line mode, and correctly add \r\n + line = line.encode() line += self.delimiter - line = line.replace(IAC, IAC + IAC).replace('\n', '\r\n') + line = line.replace(IAC, IAC + IAC).replace(b'\n', b'\r\n') if not self.protocol_flags.get("NOGOAHEAD", True): line += IAC + GA return self.transport.write(mccp_compress(self, line)) @@ -327,7 +328,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): strip_ansi=nocolor, xterm256=xterm256) if mxp: prompt = mxp_parse(prompt) - prompt = prompt.replace(IAC, IAC + IAC).replace('\n', '\r\n') + prompt = prompt.encode() + prompt = prompt.replace(IAC, IAC + IAC).replace(b'\n', b'\r\n') prompt += IAC + GA self.transport.write(mccp_compress(self, prompt)) else: diff --git a/evennia/server/portal/telnet_oob.py b/evennia/server/portal/telnet_oob.py index 2d3060ca9c..d4561dbca7 100644 --- a/evennia/server/portal/telnet_oob.py +++ b/evennia/server/portal/telnet_oob.py @@ -28,24 +28,22 @@ header where applicable. from builtins import object import re import json -from evennia.utils.utils import to_str +from evennia.utils.utils import to_str, is_iter # MSDP-relevant telnet cmd/opt-codes -MSDP = chr(69) -MSDP_VAR = chr(1) # ^A -MSDP_VAL = chr(2) # ^B -MSDP_TABLE_OPEN = chr(3) # ^C -MSDP_TABLE_CLOSE = chr(4) # ^D -MSDP_ARRAY_OPEN = chr(5) # ^E -MSDP_ARRAY_CLOSE = chr(6) # ^F +MSDP = b'\x45' +MSDP_VAR = b'\x01' # ^A +MSDP_VAL = b'\x02' # ^B +MSDP_TABLE_OPEN = b'\x03' # ^C +MSDP_TABLE_CLOSE = b'\x04' # ^D +MSDP_ARRAY_OPEN = b'\x05' # ^E +MSDP_ARRAY_CLOSE = b'\x06' # ^F # GMCP -GMCP = chr(201) +GMCP = b'\xc9' # General Telnet -IAC = chr(255) -SB = chr(250) -SE = chr(240) +from twisted.conch.telnet import IAC, SB, SE def force_str(inp): @@ -55,17 +53,17 @@ def force_str(inp): # pre-compiled regexes # returns 2-tuple -msdp_regex_table = re.compile(r"%s\s*(\w*?)\s*%s\s*%s(.*?)%s" +msdp_regex_table = re.compile(br"%s\s*(\w*?)\s*%s\s*%s(.*?)%s" % (MSDP_VAR, MSDP_VAL, MSDP_TABLE_OPEN, MSDP_TABLE_CLOSE)) # returns 2-tuple -msdp_regex_array = re.compile(r"%s\s*(\w*?)\s*%s\s*%s(.*?)%s" +msdp_regex_array = re.compile(br"%s\s*(\w*?)\s*%s\s*%s(.*?)%s" % (MSDP_VAR, MSDP_VAL, MSDP_ARRAY_OPEN, MSDP_ARRAY_CLOSE)) -msdp_regex_var = re.compile(r"%s" % MSDP_VAR) -msdp_regex_val = re.compile(r"%s" % MSDP_VAL) +msdp_regex_var = re.compile(br"%s" % MSDP_VAR) +msdp_regex_val = re.compile(br"%s" % MSDP_VAL) EVENNIA_TO_GMCP = {"client_options": "Core.Supports.Get", "get_inputfuncs": "Core.Commands.Get", @@ -178,7 +176,7 @@ class TelnetOOB(object): msdp_var=MSDP_VAR, msdp_cmdname=cmdname, msdp_val=MSDP_VAL) if not (args or kwargs): - return msdp_cmdname + return msdp_cmdname.encode() # print("encode_msdp in:", cmdname, args, kwargs) # DEBUG @@ -213,7 +211,7 @@ class TelnetOOB(object): msdp_string = msdp_args + msdp_kwargs # print("msdp_string:", msdp_string) # DEBUG - return msdp_string + return msdp_string.encode() def encode_gmcp(self, cmdname, *args, **kwargs): """ @@ -249,7 +247,7 @@ class TelnetOOB(object): gmcp_string = "%s %s" % (cmdname, json.dumps(kwargs)) # print("gmcp string", gmcp_string) # DEBUG - return gmcp_string + return gmcp_string.encode() def decode_msdp(self, data): """ @@ -275,8 +273,8 @@ class TelnetOOB(object): identified as separate cmdnames. """ - if hasattr(data, "__iter__"): - data = "".join(data) + if isinstance(data, list): + data = b"".join(data) # print("decode_msdp in:", data) # DEBUG @@ -286,29 +284,34 @@ class TelnetOOB(object): # decode tables for key, table in msdp_regex_table.findall(data): + key = key.decode() tables[key] = {} if key not in tables else tables[key] for varval in msdp_regex_var.split(table)[1:]: var, val = msdp_regex_val.split(varval, 1) + var, val = var.decode(), val.decode() if var: tables[key][var] = val # decode arrays from all that was not a table - data_no_tables = msdp_regex_table.sub("", data) + data_no_tables = msdp_regex_table.sub(b"", data) for key, array in msdp_regex_array.findall(data_no_tables): + key = key.decode() arrays[key] = [] if key not in arrays else arrays[key] parts = msdp_regex_val.split(array) + parts = [part.decode() for part in parts] if len(parts) == 2: arrays[key].append(parts[1]) elif len(parts) > 1: arrays[key].extend(parts[1:]) # decode remainders from all that were not tables or arrays - data_no_tables_or_arrays = msdp_regex_array.sub("", data_no_tables) + data_no_tables_or_arrays = msdp_regex_array.sub(b"", data_no_tables) for varval in msdp_regex_var.split(data_no_tables_or_arrays): # get remaining varvals after cleaning away tables/arrays. If mathcing # an existing key in arrays, it will be added as an argument to that command, # otherwise it will be treated as a command without argument. parts = msdp_regex_val.split(varval) + parts = [part.decode() for part in parts] if len(parts) == 2: variables[parts[0]] = parts[1] elif len(parts) > 1: @@ -356,33 +359,33 @@ class TelnetOOB(object): Core.Name [[args], {kwargs}] -> [name, [args], {kwargs}] """ - if hasattr(data, "__iter__"): - data = "".join(data) + if isinstance(data, list): + data = b"".join(data) # print("decode_gmcp in:", data) # DEBUG if data: try: cmdname, structure = data.split(None, 1) except ValueError: - cmdname, structure = data, "" - cmdname = cmdname.replace(".", "_") + cmdname, structure = data, b"" + cmdname = cmdname.replace(b".", b"_") try: structure = json.loads(structure) except ValueError: # maybe the structure is not json-serialized at all pass args, kwargs = [], {} - if hasattr(structure, "__iter__"): + if is_iter(structure): if isinstance(structure, dict): kwargs = {key: value for key, value in structure.items() if key} else: args = list(structure) else: args = (structure,) - if cmdname.lower().startswith("core_"): + if cmdname.lower().startswith(b"core_"): # if Core.cmdname, then use cmdname cmdname = cmdname[5:] - self.protocol.data_in(**{cmdname.lower(): [args, kwargs]}) + self.protocol.data_in(**{cmdname.lower().decode(): [args, kwargs]}) # access methods diff --git a/evennia/server/portal/ttype.py b/evennia/server/portal/ttype.py index 96ae1c1100..4148129dba 100644 --- a/evennia/server/portal/ttype.py +++ b/evennia/server/portal/ttype.py @@ -13,9 +13,9 @@ under the 'TTYPE' key. from builtins import object # telnet option codes -TTYPE = chr(24) -IS = chr(0) -SEND = chr(1) +TTYPE = b'\x18' +IS = b'\x00' +SEND = b'\x01' # terminal capabilities and their codes MTTS = [(128, 'PROXY'), @@ -89,7 +89,7 @@ class Ttype(object): return try: - option = "".join(option).lstrip(IS) + option = b"".join(option).lstrip(IS).decode() except TypeError: # option is not on a suitable form for joining pass From 7477cc56e0ef05eed4116d2d1b1ee293946f464c Mon Sep 17 00:00:00 2001 From: Ryan Stein Date: Sun, 29 Oct 2017 21:45:17 -0400 Subject: [PATCH 009/493] Remove use of unavailable django force_unicode(). --- evennia/server/portal/webclient_ajax.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/evennia/server/portal/webclient_ajax.py b/evennia/server/portal/webclient_ajax.py index d780feabf7..02759b79fd 100644 --- a/evennia/server/portal/webclient_ajax.py +++ b/evennia/server/portal/webclient_ajax.py @@ -23,7 +23,6 @@ import time from twisted.web import server, resource from twisted.internet.task import LoopingCall from django.utils.functional import Promise -from django.utils.encoding import force_unicode from django.conf import settings from evennia.utils.ansi import parse_ansi from evennia.utils import utils @@ -44,7 +43,7 @@ _KEEPALIVE = 30 # how often to check keepalive class LazyEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, Promise): - return force_unicode(obj) + return str(obj) return super(LazyEncoder, self).default(obj) From eabdf275652cb30b7f20124482b45764d0334cea Mon Sep 17 00:00:00 2001 From: Ryan Stein Date: Sun, 29 Oct 2017 21:54:06 -0400 Subject: [PATCH 010/493] Django WSGIHandler is deprecated. Port webserver for Py3. --- evennia/server/webserver.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/evennia/server/webserver.py b/evennia/server/webserver.py index a06f537dac..ab1fb43a7e 100644 --- a/evennia/server/webserver.py +++ b/evennia/server/webserver.py @@ -1,15 +1,16 @@ """ -This implements resources for twisted webservers using the wsgi -interface of django. This alleviates the need of running e.g. an -apache server to serve Evennia's web presence (although you could do +This implements resources for Twisted webservers using the WSGI +interface of Django. This alleviates the need of running e.g. an +Apache server to serve Evennia's web presence (although you could do that too if desired). The actual servers are started inside server.py as part of the Evennia application. -(Lots of thanks to http://githup.com/clemensha/twisted-wsgi-django for +(Lots of thanks to http://github.com/clemesha/twisted-wsgi-django for a great example/aid on how to do this.) + """ import urllib.parse from urllib.parse import quote as urlquote @@ -23,7 +24,8 @@ from twisted.internet import defer from twisted.web.wsgi import WSGIResource from django.conf import settings -from django.core.handlers.wsgi import WSGIHandler +from django.core.wsgi import get_wsgi_application + from evennia.utils import logger @@ -119,9 +121,10 @@ class EvenniaReverseProxyResource(ReverseProxyResource): request.content.seek(0, 0) qs = urllib.parse.urlparse(request.uri)[4] if qs: - rest = self.path + '?' + qs + rest = self.path + '?' + qs.decode() else: rest = self.path + rest = rest.encode() clientFactory = self.proxyClientFactoryClass( request.method, rest, request.clientproto, request.getAllHeaders(), request.content.read(), request) @@ -156,8 +159,8 @@ class DjangoWebRoot(resource.Resource): self.pool = pool self._echo_log = True self._pending_requests = {} - resource.Resource.__init__(self) - self.wsgi_resource = WSGIResource(reactor, pool, WSGIHandler()) + super().__init__() + self.wsgi_resource = WSGIResource(reactor, pool, get_wsgi_application()) def empty_threadpool(self): """ @@ -242,14 +245,14 @@ class WSGIWebServer(internet.TCPServer): """ self.pool = pool - internet.TCPServer.__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) def startService(self): """ Start the pool after the service starts. """ - internet.TCPServer.startService(self) + super().startService() self.pool.start() def stopService(self): @@ -257,5 +260,5 @@ class WSGIWebServer(internet.TCPServer): Safely stop the pool after the service stops. """ - internet.TCPServer.stopService(self) + super().stopService() self.pool.stop() From 00a87bcdcf0bd1c42b33f4fecc8afa7ea99607c9 Mon Sep 17 00:00:00 2001 From: Ryan Stein Date: Sun, 29 Oct 2017 21:57:40 -0400 Subject: [PATCH 011/493] Port usage of Twisted AMP for Py3. --- evennia/server/amp.py | 73 +++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/evennia/server/amp.py b/evennia/server/amp.py index 615f9b3799..6cdeac5d23 100644 --- a/evennia/server/amp.py +++ b/evennia/server/amp.py @@ -22,11 +22,8 @@ import os import time from collections import defaultdict, namedtuple from itertools import count -from io import StringIO -try: - import pickle as pickle -except ImportError: - import pickle +from io import BytesIO +import pickle from twisted.protocols import amp from twisted.internet import protocol from twisted.internet.defer import Deferred @@ -39,17 +36,17 @@ DUMMYSESSION = namedtuple('DummySession', ['sessid'])(0) # communication bits # (chr(9) and chr(10) are \t and \n, so skipping them) -PCONN = chr(1) # portal session connect -PDISCONN = chr(2) # portal session disconnect -PSYNC = chr(3) # portal session sync -SLOGIN = chr(4) # server session login -SDISCONN = chr(5) # server session disconnect -SDISCONNALL = chr(6) # server session disconnect all -SSHUTD = chr(7) # server shutdown -SSYNC = chr(8) # server session sync -SCONN = chr(11) # server creating new connection (for irc bots and etc) -PCONNSYNC = chr(12) # portal post-syncing a session -PDISCONNALL = chr(13) # portal session disconnect all +PCONN = b'\x01' # portal session connect +PDISCONN = b'\x02' # portal session disconnect +PSYNC = b'\x03' # portal session sync +SLOGIN = b'\x04' # server session login +SDISCONN = b'\x05' # server session disconnect +SDISCONNALL = b'\x06' # server session disconnect all +SSHUTD = b'\x07' # server shutdown +SSYNC = b'\x08' # server session sync +SCONN = b'\x0b' # server creating new connection (for irc bots and etc) +PCONNSYNC = b'\x0c' # portal post-syncing a session +PDISCONNALL = b'\x0d' # portal session disconnect all AMP_MAXLEN = amp.MAX_VALUE_LENGTH # max allowed data length in AMP protocol (cannot be changed) BATCH_RATE = 250 # max commands/sec before switching to batch-sending @@ -214,28 +211,28 @@ class Compressed(amp.String): Converts from box representation to python. We group very long data into batches. """ - value = StringIO() + value = BytesIO() value.write(strings.get(name)) for counter in count(2): # count from 2 upwards - chunk = strings.get("%s.%d" % (name, counter)) + chunk = strings.get(b"%s.%d" % (name, counter)) if chunk is None: break value.write(chunk) - objects[name] = value.getvalue() + objects[name.decode()] = value.getvalue() def toBox(self, name, strings, objects, proto): """ Convert from data to box. We handled too-long batched data and put it together here. """ - value = StringIO(objects[name]) + value = BytesIO(objects[name.decode()]) strings[name] = value.read(AMP_MAXLEN) for counter in count(2): chunk = value.read(AMP_MAXLEN) if not chunk: break - strings["%s.%d" % (name, counter)] = chunk + strings[b"%s.%d" % (name, counter)] = chunk def toString(self, inObject): """ @@ -256,8 +253,8 @@ class MsgPortal2Server(amp.Command): """ key = "MsgPortal2Server" - arguments = [('packed_data', Compressed())] - errors = {Exception: 'EXCEPTION'} + arguments = [(b'packed_data', Compressed())] + errors = {Exception: b'EXCEPTION'} response = [] @@ -267,8 +264,8 @@ class MsgServer2Portal(amp.Command): """ key = "MsgServer2Portal" - arguments = [('packed_data', Compressed())] - errors = {Exception: 'EXCEPTION'} + arguments = [(b'packed_data', Compressed())] + errors = {Exception: b'EXCEPTION'} response = [] @@ -281,8 +278,8 @@ class AdminPortal2Server(amp.Command): """ key = "AdminPortal2Server" - arguments = [('packed_data', Compressed())] - errors = {Exception: 'EXCEPTION'} + arguments = [(b'packed_data', Compressed())] + errors = {Exception: b'EXCEPTION'} response = [] @@ -295,8 +292,8 @@ class AdminServer2Portal(amp.Command): """ key = "AdminServer2Portal" - arguments = [('packed_data', Compressed())] - errors = {Exception: 'EXCEPTION'} + arguments = [(b'packed_data', Compressed())] + errors = {Exception: b'EXCEPTION'} response = [] @@ -309,22 +306,22 @@ class FunctionCall(amp.Command): """ key = "FunctionCall" - arguments = [('module', amp.String()), - ('function', amp.String()), - ('args', amp.String()), - ('kwargs', amp.String())] - errors = {Exception: 'EXCEPTION'} - response = [('result', amp.String())] + arguments = [(b'module', amp.String()), + (b'function', amp.String()), + (b'args', amp.String()), + (b'kwargs', amp.String())] + errors = {Exception: b'EXCEPTION'} + response = [(b'result', amp.String())] # Helper functions for pickling. def dumps(data): - return to_str(pickle.dumps(to_str(data), pickle.HIGHEST_PROTOCOL)) + return pickle.dumps(data, pickle.HIGHEST_PROTOCOL) def loads(data): - return pickle.loads(to_str(data)) + return pickle.loads(data) # ------------------------------------------------------------- @@ -483,7 +480,7 @@ class AMPProtocol(amp.AMP): Args: session (Session): Unique Session. - kwargs (any, optiona): Extra data. + kwargs (any, optional): Extra data. """ return self.send_data(MsgServer2Portal, session.sessid, **kwargs) From be5ecf0d0d72ed5ad4300465704da8283937292a Mon Sep 17 00:00:00 2001 From: Ryan Stein Date: Sun, 29 Oct 2017 21:58:30 -0400 Subject: [PATCH 012/493] Convert to bytes to allow access to static and media resources. --- evennia/server/server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evennia/server/server.py b/evennia/server/server.py index a68f6d5463..457302b5f2 100644 --- a/evennia/server/server.py +++ b/evennia/server/server.py @@ -569,9 +569,9 @@ if WEBSERVER_ENABLED: web_root = DjangoWebRoot(threads) # point our media resources to url /media - web_root.putChild("media", static.File(settings.MEDIA_ROOT)) + web_root.putChild(b"media", static.File(settings.MEDIA_ROOT)) # point our static resources to url /static - web_root.putChild("static", static.File(settings.STATIC_ROOT)) + web_root.putChild(b"static", static.File(settings.STATIC_ROOT)) EVENNIA.web_root = web_root if WEB_PLUGINS_MODULE: From a4b902108c5a9a7abcb98bbb5872161ea91e96a1 Mon Sep 17 00:00:00 2001 From: Ryan Stein Date: Sun, 29 Oct 2017 22:10:16 -0400 Subject: [PATCH 013/493] Add __lt__ method necessary for usage of sorted() later in the file. --- evennia/typeclasses/tags.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/evennia/typeclasses/tags.py b/evennia/typeclasses/tags.py index e92c0ece42..1f1e9b54ea 100644 --- a/evennia/typeclasses/tags.py +++ b/evennia/typeclasses/tags.py @@ -66,6 +66,9 @@ class Tag(models.Model): unique_together = (('db_key', 'db_category', 'db_tagtype', 'db_model'),) index_together = (('db_key', 'db_category', 'db_tagtype', 'db_model'),) + def __lt__(self, other): + return str(self) < str(other) + def __unicode__(self): return "" % (self.db_key, "(category:%s)" % self.db_category if self.db_category else "") From 8d0d3a942fa8f448aaf904be2dc6120891d71924 Mon Sep 17 00:00:00 2001 From: Ryan Stein Date: Sun, 29 Oct 2017 22:14:22 -0400 Subject: [PATCH 014/493] Partially port EvTable for Py3. --- evennia/utils/evtable.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/evennia/utils/evtable.py b/evennia/utils/evtable.py index b791b9911f..fadf64fd4a 100644 --- a/evennia/utils/evtable.py +++ b/evennia/utils/evtable.py @@ -115,13 +115,12 @@ table string. """ -from builtins import object, range from future.utils import listitems from django.conf import settings from textwrap import TextWrapper from copy import deepcopy, copy -from evennia.utils.utils import to_unicode, m_len +from evennia.utils.utils import to_unicode, m_len, is_iter from evennia.utils.ansi import ANSIString _DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH @@ -135,7 +134,7 @@ def _to_ansi(obj): obj (str): Convert incoming text to be ANSI aware ANSIStrings. """ - if hasattr(obj, "__iter__"): + if is_iter(obj): return [_to_ansi(o) for o in obj] else: return ANSIString(to_unicode(obj)) @@ -187,13 +186,20 @@ class ANSITextWrapper(TextWrapper): 'use', ' ', 'the', ' ', '-b', ' ', option!' otherwise. """ - # only use unicode wrapper - if self.break_on_hyphens: - pat = self.wordsep_re_uni - else: - pat = self.wordsep_simple_re_uni - chunks = pat.split(_to_ansi(text)) - return [chunk for chunk in chunks if chunk] # remove empty chunks + # NOTE-PYTHON3: The following code only roughly approximates what this + # function used to do. Regex splitting on ANSIStrings is + # dropping ANSI codes, so we're using ANSIString.split + # for the time being. + # + # A less hackier solution would be appreciated. + chunks = _to_ansi(text).split() + + chunks = [chunk+' ' for chunk in chunks if chunk] # remove empty chunks + + if len(chunks) > 1: + chunks[-1] = chunks[-1][0:-1] + + return chunks def _wrap_chunks(self, chunks): """_wrap_chunks(chunks : [string]) -> [string] From aaf13eec16797ca2173bcc7ea88cc0ade303fb3c Mon Sep 17 00:00:00 2001 From: Ryan Stein Date: Sun, 29 Oct 2017 22:14:39 -0400 Subject: [PATCH 015/493] Port EvMenu and EvForm for Py3. --- evennia/utils/evform.py | 7 +++---- evennia/utils/evmenu.py | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/evennia/utils/evform.py b/evennia/utils/evform.py index 44d9f86cf4..488a0c46a4 100644 --- a/evennia/utils/evform.py +++ b/evennia/utils/evform.py @@ -140,7 +140,7 @@ from builtins import object, range import re import copy from evennia.utils.evtable import EvCell, EvTable -from evennia.utils.utils import all_from_module, to_str, to_unicode +from evennia.utils.utils import all_from_module, to_str, to_unicode, is_iter from evennia.utils.ansi import ANSIString # non-valid form-identifying characters (which can thus be @@ -161,7 +161,7 @@ def _to_ansi(obj, regexable=False): obj = _ANSI_ESCAPE.sub(r"||||", obj) if isinstance(obj, dict): return dict((key, _to_ansi(value, regexable=regexable)) for key, value in list(obj.items())) - elif hasattr(obj, "__iter__"): + elif is_iter(obj): return [_to_ansi(o) for o in obj] else: return ANSIString(to_unicode(obj), regexable=regexable) @@ -260,7 +260,6 @@ class EvForm(object): # get rectangles and assign EvCells for key, (iy, leftix, rightix) in list(cell_coords.items()): - # scan up to find top of rectangle dy_up = 0 if iy > 0: @@ -420,7 +419,7 @@ class EvForm(object): def __str__(self): "Prints the form" - return ANSIString("\n").join([line for line in self.form]) + return str(ANSIString("\n").join([line for line in self.form])) def __unicode__(self): "prints the form" diff --git a/evennia/utils/evmenu.py b/evennia/utils/evmenu.py index d3cd7ddb64..c5bdd730a6 100644 --- a/evennia/utils/evmenu.py +++ b/evennia/utils/evmenu.py @@ -167,7 +167,7 @@ from evennia import Command, CmdSet from evennia.utils import logger from evennia.utils.evtable import EvTable from evennia.utils.ansi import strip_ansi -from evennia.utils.utils import mod_import, make_iter, pad, m_len +from evennia.utils.utils import mod_import, make_iter, pad, m_len, is_iter from evennia.commands import cmdhandler # read from protocol NAWS later? @@ -726,7 +726,7 @@ class EvMenu(object): # validation of the node return values helptext = "" - if hasattr(nodetext, "__iter__"): + if is_iter(nodetext): if len(nodetext) > 1: nodetext, helptext = nodetext[:2] else: From 9d48e616b1758c1f6fdbf56dae6d8c249d543475 Mon Sep 17 00:00:00 2001 From: Ryan Stein Date: Sun, 29 Oct 2017 22:17:00 -0400 Subject: [PATCH 016/493] Port SessionHandler for Py3. --- evennia/server/sessionhandler.py | 59 +++++++++++++++----------------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/evennia/server/sessionhandler.py b/evennia/server/sessionhandler.py index 734e6e0c27..89d6afde89 100644 --- a/evennia/server/sessionhandler.py +++ b/evennia/server/sessionhandler.py @@ -24,11 +24,9 @@ from evennia.utils.utils import (variable_from_module, is_iter, make_iter, callables_from_module) from evennia.utils.inlinefuncs import parse_inlinefunc +from codecs import decode as codecs_decode -try: - import pickle as pickle -except ImportError: - import pickle +import pickle _INLINEFUNC_ENABLED = settings.INLINEFUNC_ENABLED @@ -39,6 +37,8 @@ _ServerConfig = None _ScriptDB = None _OOB_HANDLER = None +_ERR_BAD_UTF8 = 'Your client sent an incorrect UTF-8 sequence.' + class DummySession(object): sessid = 0 @@ -47,17 +47,8 @@ class DummySession(object): DUMMYSESSION = DummySession() # AMP signals -PCONN = chr(1) # portal session connect -PDISCONN = chr(2) # portal session disconnect -PSYNC = chr(3) # portal session sync -SLOGIN = chr(4) # server session login -SDISCONN = chr(5) # server session disconnect -SDISCONNALL = chr(6) # server session disconnect all -SSHUTD = chr(7) # server shutdown -SSYNC = chr(8) # server session sync -SCONN = chr(11) # server portal connection (for bots) -PCONNSYNC = chr(12) # portal post-syncing session -PDISCONNALL = chr(13) # portal session discnnect all +from .amp import (PCONN, PDISCONN, PSYNC, SLOGIN, SDISCONN, SDISCONNALL, + SSHUTD, SSYNC, SCONN, PCONNSYNC, PDISCONNALL, ) # i18n from django.utils.translation import ugettext as _ @@ -185,6 +176,21 @@ class SessionHandler(dict): raw = options.get("raw", False) strip_inlinefunc = options.get("strip_inlinefunc", False) + def _utf8(data): + if isinstance(data, bytes): + try: + data = codecs_decode(data, session.protocol_flags["ENCODING"]) + except LookupError: + # wrong encoding set on the session. Set it to a safe one + session.protocol_flags["ENCODING"] = "utf-8" + data = codecs_decode(data, "utf-8") + except UnicodeDecodeError: + # incorrect unicode sequence + session.sendLine(_ERR_BAD_UTF8) + data = '' + + return data + def _validate(data): "Helper function to convert data to AMP-safe (picketable) values" if isinstance(data, dict): @@ -192,24 +198,15 @@ class SessionHandler(dict): for key, part in list(data.items()): newdict[key] = _validate(part) return newdict - elif hasattr(data, "__iter__"): + elif is_iter(data): return [_validate(part) for part in data] - elif isinstance(data, str): - # make sure strings are in a valid encoding - try: - data = data and to_str(to_unicode(data), encoding=session.protocol_flags["ENCODING"]) - except LookupError: - # wrong encoding set on the session. Set it to a safe one - session.protocol_flags["ENCODING"] = "utf-8" - data = to_str(to_unicode(data), encoding=session.protocol_flags["ENCODING"]) + elif isinstance(data, (str, bytes, )): + data = _utf8(data) + if _INLINEFUNC_ENABLED and not raw and isinstance(self, ServerSessionHandler): # only parse inlinefuncs on the outgoing path (sessionhandler->) data = parse_inlinefunc(data, strip=strip_inlinefunc, session=session) - # At this point the object is certainly the right encoding, but may still be a unicode object-- - # to_str does not actually force objects to become bytestrings. - # If the unicode object is a subclass of unicode, such as ANSIString, this can cause a problem, - # as special behavior for that class will still be in play. Since we're now transferring raw data, - # we must now force this to be a proper bytestring. + return str(data) elif hasattr(data, "id") and hasattr(data, "db_date_created") \ and hasattr(data, '__dbclass__'): @@ -229,10 +226,10 @@ class SessionHandler(dict): rkwargs[key] = [[], {}] elif isinstance(data, dict): rkwargs[key] = [[], _validate(data)] - elif hasattr(data, "__iter__"): + elif is_iter(data): if isinstance(data[-1], dict): if len(data) == 2: - if hasattr(data[0], "__iter__"): + if is_iter(data[0]): rkwargs[key] = [_validate(data[0]), _validate(data[1])] else: rkwargs[key] = [[_validate(data[0])], _validate(data[1])] From b5cf27fc181d0d80d127fcd51d388da9265cc530 Mon Sep 17 00:00:00 2001 From: Ryan Stein Date: Sun, 29 Oct 2017 22:20:23 -0400 Subject: [PATCH 017/493] Fix ServerConfig model for Py3. --- evennia/server/models.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/evennia/server/models.py b/evennia/server/models.py index 92cfe88ed6..c31d203532 100644 --- a/evennia/server/models.py +++ b/evennia/server/models.py @@ -8,12 +8,7 @@ Config values should usually be set through the manager's conf() method. """ -from builtins import object - -try: - import pickle as pickle -except ImportError: - import pickle +import pickle from django.db import models from evennia.utils.idmapper.models import WeakSharedMemoryModel @@ -48,7 +43,7 @@ class ServerConfig(WeakSharedMemoryModel): # main name of the database entry db_key = models.CharField(max_length=64, unique=True) # config value - db_value = models.TextField(blank=True) + db_value = models.BinaryField(blank=True) objects = ServerConfigManager() _is_deleted = False @@ -83,7 +78,7 @@ class ServerConfig(WeakSharedMemoryModel): #@property def __value_get(self): "Getter. Allows for value = self.value" - return pickle.loads(str(self.db_value)) + return pickle.loads(self.db_value) #@value.setter def __value_set(self, value): From ee58e59e7eb4069d8290c323a29bcb6f6e7366f7 Mon Sep 17 00:00:00 2001 From: Ryan Stein Date: Sun, 29 Oct 2017 22:21:38 -0400 Subject: [PATCH 018/493] Port a few miscellaneous items. --- evennia/commands/default/comms.py | 3 +-- evennia/typeclasses/attributes.py | 4 +++- evennia/utils/ansi.py | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/evennia/commands/default/comms.py b/evennia/commands/default/comms.py index 8ab75f8727..c806a08296 100644 --- a/evennia/commands/default/comms.py +++ b/evennia/commands/default/comms.py @@ -7,7 +7,6 @@ make sure to homogenize self.caller to always be the account object for easy handling. """ -from past.builtins import cmp from django.conf import settings from evennia.comms.models import ChannelDB, Msg from evennia.accounts.models import AccountDB @@ -711,7 +710,7 @@ class CmdPage(COMMAND_DEFAULT_CLASS): if not self.args or not self.rhs: pages = pages_we_sent + pages_we_got - pages.sort(lambda x, y: cmp(x.date_created, y.date_created)) + pages = sorted(pages, key=lambda page: page.date_created) number = 5 if self.args: diff --git a/evennia/typeclasses/attributes.py b/evennia/typeclasses/attributes.py index 2fdbc5d6b7..1d3b820226 100644 --- a/evennia/typeclasses/attributes.py +++ b/evennia/typeclasses/attributes.py @@ -753,7 +753,9 @@ def initialize_nick_templates(in_template, out_template): # create the regex for in_template regex_string = fnmatch.translate(in_template) # we must account for a possible line break coming over the wire - regex_string = regex_string[:-7] + r"(?:[\n\r]*?)\Z(?ms)" + + # NOTE-PYTHON3: fnmatch.translate format changed since Python2 + regex_string = regex_string[:-2] + r"(?:[\n\r]*?)\Z" # validate the templates regex_args = [match.group(2) for match in _RE_NICK_ARG.finditer(regex_string)] diff --git a/evennia/utils/ansi.py b/evennia/utils/ansi.py index a1a3ebf0e1..e69845abb8 100644 --- a/evennia/utils/ansi.py +++ b/evennia/utils/ansi.py @@ -708,7 +708,7 @@ class ANSIString(with_metaclass(ANSIMeta, str)): if not isinstance(string, str): string = string.decode('utf-8') - ansi_string = super(ANSIString, cls).__new__(ANSIString, to_str(clean_string), "utf-8") + ansi_string = super(ANSIString, cls).__new__(ANSIString, to_str(clean_string)) ansi_string._raw_string = string ansi_string._clean_string = clean_string ansi_string._code_indexes = code_indexes @@ -716,7 +716,7 @@ class ANSIString(with_metaclass(ANSIMeta, str)): return ansi_string def __str__(self): - return self._raw_string.encode('utf-8') + return self._raw_string def __unicode__(self): """ @@ -849,7 +849,7 @@ class ANSIString(with_metaclass(ANSIMeta, str)): if not slice_indexes: return ANSIString('') try: - string = self[slc.start]._raw_string + string = self[slc.start or 0]._raw_string except IndexError: return ANSIString('') last_mark = slice_indexes[0] From f9526e78a8037f480860613d05f3ecaa0989af74 Mon Sep 17 00:00:00 2001 From: Ryan Stein Date: Sun, 29 Oct 2017 22:39:54 -0400 Subject: [PATCH 019/493] Implement hashing functions for Command and ServerSession. --- evennia/commands/command.py | 13 +++++++++++++ evennia/server/serversession.py | 9 +++++++++ 2 files changed, 22 insertions(+) diff --git a/evennia/commands/command.py b/evennia/commands/command.py index c30c74222e..094934a02e 100644 --- a/evennia/commands/command.py +++ b/evennia/commands/command.py @@ -201,6 +201,19 @@ class Command(with_metaclass(CommandMeta, object)): # probably got a string return cmd in self._matchset + def __hash__(self): + """ + Python 3 requires that any class which implements __eq__ must also + implement __hash__ and that the corresponding hashes for equivalent + instances are themselves equivalent. + + Technically, the following implementation is only valid for comparison + against other Commands, as our __eq__ supports comparison against + str, too. + + """ + return hash('\n'.join(self._matchset)) + def __ne__(self, cmd): """ The logical negation of __eq__. Since this is one of the most diff --git a/evennia/server/serversession.py b/evennia/server/serversession.py index acd59ad67f..c86e189b64 100644 --- a/evennia/server/serversession.py +++ b/evennia/server/serversession.py @@ -433,6 +433,15 @@ class ServerSession(Session): except AttributeError: return False + def __hash__(self): + """ + Python 3 requires that any class which implements __eq__ must also + implement __hash__ and that the corresponding hashes for equivalent + instances are themselves equivalent. + + """ + return hash(self.address) + def __ne__(self, other): try: return self.address != other.address From 75d74c252ed41f122952a0815ff1740917191bc3 Mon Sep 17 00:00:00 2001 From: Ryan Stein Date: Sun, 29 Oct 2017 22:54:43 -0400 Subject: [PATCH 020/493] Remove sitecustomize.py, unnecessary for Py3 now. --- sitecustomize.py | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 sitecustomize.py diff --git a/sitecustomize.py b/sitecustomize.py deleted file mode 100644 index 39b6e373a3..0000000000 --- a/sitecustomize.py +++ /dev/null @@ -1,11 +0,0 @@ -""" -This special Python config file sets the default encoding for -the codebase to UTF-8 instead of ascii. This allows for just -about any language to be used in-game. - -It is not advisable to change the value set below, as -there will be a lot of encoding errors that result in -server crashes. -""" -import sys -sys.setdefaultencoding('utf-8') From 5074c112af98f39f1b193eeaf06f003910da9006 Mon Sep 17 00:00:00 2001 From: Ryan Stein Date: Sun, 29 Oct 2017 22:58:04 -0400 Subject: [PATCH 021/493] We need the latest version of Twisted possible for Py3. --- requirements.txt | 2 +- win_requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 7ce920f3ce..b378247e36 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ # Evennia dependencies, for Linux/Mac platforms django > 1.10, < 2.0 -twisted == 16.0.0 +twisted >= 17.0.0 mock >= 1.0.1 pillow == 2.9.0 pytz diff --git a/win_requirements.txt b/win_requirements.txt index a89abc615b..75e922013f 100644 --- a/win_requirements.txt +++ b/win_requirements.txt @@ -4,7 +4,7 @@ pypiwin32 # general django > 1.10, < 2.0 -twisted >= 16.0.0 +twisted >= 17.0.0 mock >= 1.0.1 pillow == 2.9.0 pytz From 93475a6de5c9502ac87861fdf2b1bf3496ea1cc0 Mon Sep 17 00:00:00 2001 From: Ryan Stein Date: Sun, 29 Oct 2017 23:29:21 -0400 Subject: [PATCH 022/493] Deprecate to_str, to_unicode. Fix class_from_module, is_iter, make_iter. --- evennia/utils/utils.py | 109 ++++++++++++++++------------------------- 1 file changed, 43 insertions(+), 66 deletions(-) diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index fbd9f88fe7..832b8a01ed 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -6,8 +6,6 @@ They provide some useful string and conversion methods that might be of use when designing your own game. """ - -from builtins import object, range from future.utils import viewkeys, raise_ import os @@ -20,6 +18,7 @@ import textwrap import random from os.path import join as osjoin from importlib import import_module +from importlib.util import find_spec, module_from_spec from inspect import ismodule, trace, getmembers, getmodule from collections import defaultdict, OrderedDict from twisted.internet import threads, reactor, task @@ -32,10 +31,7 @@ _MULTIMATCH_TEMPLATE = settings.SEARCH_MULTIMATCH_TEMPLATE _EVENNIA_DIR = settings.EVENNIA_DIR _GAME_DIR = settings.GAME_DIR -try: - import pickle as pickle -except ImportError: - import pickle +import pickle ENCODINGS = settings.ENCODINGS _GA = object.__getattribute__ @@ -45,15 +41,15 @@ _DA = object.__delattr__ _DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH -def is_iter(iterable): +def is_iter(obj): """ Checks if an object behaves iterably. Args: - iterable (any): Entity to check for iterability. + obj (any): Entity to check for iterability. Returns: - is_iterable (bool): If `iterable` is iterable or not. + is_iterable (bool): If `obj` is iterable or not. Notes: Strings are *not* accepted as iterable (although they are @@ -61,7 +57,13 @@ def is_iter(iterable): what we want to do with a string. """ - return hasattr(iterable, '__iter__') + if isinstance(obj, (str, bytes, )): + return False + + try: + return iter(obj) and True + except TypeError: + return False def make_iter(obj): @@ -76,7 +78,7 @@ def make_iter(obj): passed-through or made iterable. """ - return not hasattr(obj, '__iter__') and [obj] or obj + return not is_iter(obj) and [obj] or obj def wrap(text, width=_DEFAULT_WIDTH, indent=0): @@ -599,7 +601,7 @@ def dbref(inp, reqhash=True): inp.startswith("#") and inp.lstrip('#').isdigit()) else None) - return num if num > 0 else None + return num if isinstance(num, int) and num > 0 else None elif isinstance(inp, str): inp = inp.lstrip('#') return int(inp) if inp.isdigit() and int(inp) > 0 else None @@ -702,6 +704,10 @@ def latinify(unicode_string, default='?', pure_ascii=False): def to_unicode(obj, encoding='utf-8', force_string=False): """ + This function is deprecated in the Python 3 version of Evennia and is + likely to be phased out in future releases. + + --- This decodes a suitable object to the unicode format. Args: @@ -723,35 +729,23 @@ def to_unicode(obj, encoding='utf-8', force_string=False): """ - if force_string and not isinstance(obj, str): + if isinstance(obj, (str, bytes, )): + return obj + + if force_string: # some sort of other object. Try to # convert it to a string representation. - if hasattr(obj, '__str__'): - obj = obj.__str__() - elif hasattr(obj, '__unicode__'): - obj = obj.__unicode__() - else: - # last resort - obj = str(obj) + obj = str(obj) - if isinstance(obj, str) and not isinstance(obj, str): - try: - obj = str(obj, encoding) - return obj - except UnicodeDecodeError: - for alt_encoding in ENCODINGS: - try: - obj = str(obj, alt_encoding) - return obj - except UnicodeDecodeError: - # if we still have an error, give up - pass - raise Exception("Error: '%s' contains invalid character(s) not in %s." % (obj, encoding)) return obj def to_str(obj, encoding='utf-8', force_string=False): """ + This function is deprecated in the Python 3 version of Evennia and is + likely to be phased out in future releases. + + --- This encodes a unicode string back to byte-representation, for printing, writing to disk etc. @@ -768,32 +762,14 @@ def to_str(obj, encoding='utf-8', force_string=False): conversion of objects to strings. """ - if force_string and not isinstance(obj, str): + if isinstance(obj, (str, bytes, )): + return obj + + if force_string: # some sort of other object. Try to # convert it to a string representation. - try: - obj = str(obj) - except Exception: - obj = str(obj) + obj = str(obj) - if isinstance(obj, str) and isinstance(obj, str): - try: - obj = obj.encode(encoding) - return obj - except UnicodeEncodeError: - for alt_encoding in ENCODINGS: - try: - obj = obj.encode(alt_encoding) - return obj - except UnicodeEncodeError: - # if we still have an error, give up - pass - - # if we get to this point we have not found any way to convert this string. Try to parse it manually, - try: - return latinify(obj, '?') - except Exception as err: - raise Exception("%s, Error: Unicode could not encode unicode string '%s'(%s) to a bytestring. " % (err, obj, encoding)) return obj @@ -1355,7 +1331,7 @@ def class_from_module(path, defaultpaths=None): Args: path (str): Full Python dot-path to module. - defaultpaths (iterable, optional): If a direc import from `path` fails, + defaultpaths (iterable, optional): If a direct import from `path` fails, try subsequent imports by prepending those paths to `path`. Returns: @@ -1376,17 +1352,18 @@ def class_from_module(path, defaultpaths=None): testpath, clsname = testpath.rsplit(".", 1) else: raise ImportError("the path '%s' is not on the form modulepath.Classname." % path) + try: - mod = import_module(testpath, package="evennia") - except ImportError: - if len(trace()) > 2: - # this means the error happened within the called module and - # we must not hide it. - exc = sys.exc_info() - raise_(exc[1], None, exc[2]) - else: - # otherwise, try the next suggested path + if not find_spec(testpath, package='evennia'): continue + except ModuleNotFoundError: + continue + + try: + mod = import_module(testpath, package='evennia') + except ModuleNotFoundError: + break + try: cls = getattr(mod, clsname) break From 0cd979327bf1419924411d49434ee7c9d47f38d7 Mon Sep 17 00:00:00 2001 From: Ryan Stein Date: Sun, 29 Oct 2017 23:47:27 -0400 Subject: [PATCH 023/493] Fix whitespace. --- evennia/server/amp.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/evennia/server/amp.py b/evennia/server/amp.py index 6cdeac5d23..0f9fec6a46 100644 --- a/evennia/server/amp.py +++ b/evennia/server/amp.py @@ -44,9 +44,9 @@ SDISCONN = b'\x05' # server session disconnect SDISCONNALL = b'\x06' # server session disconnect all SSHUTD = b'\x07' # server shutdown SSYNC = b'\x08' # server session sync -SCONN = b'\x0b' # server creating new connection (for irc bots and etc) -PCONNSYNC = b'\x0c' # portal post-syncing a session -PDISCONNALL = b'\x0d' # portal session disconnect all +SCONN = b'\x0b' # server creating new connection (for irc bots and etc) +PCONNSYNC = b'\x0c' # portal post-syncing a session +PDISCONNALL = b'\x0d' # portal session disconnect all AMP_MAXLEN = amp.MAX_VALUE_LENGTH # max allowed data length in AMP protocol (cannot be changed) BATCH_RATE = 250 # max commands/sec before switching to batch-sending From cd21fb23962063877f4d1a0568a55907d3032860 Mon Sep 17 00:00:00 2001 From: Ryan Stein Date: Thu, 2 Nov 2017 10:41:41 -0400 Subject: [PATCH 024/493] Use Python 3's new super() convention. --- evennia/accounts/accounts.py | 8 ++++---- evennia/accounts/bots.py | 14 ++++++------- evennia/commands/cmdset.py | 2 +- evennia/commands/command.py | 2 +- evennia/commands/default/account.py | 2 +- evennia/commands/default/building.py | 4 ++-- evennia/commands/default/muxcommand.py | 4 ++-- evennia/commands/default/tests.py | 2 +- evennia/commands/tests.py | 12 +++++------ evennia/comms/admin.py | 2 +- evennia/comms/comms.py | 2 +- evennia/contrib/chargen.py | 2 +- evennia/contrib/clothing.py | 2 +- evennia/contrib/egi_client/service.py | 4 ++-- evennia/contrib/email_login.py | 2 +- evennia/contrib/extended_room.py | 2 +- evennia/contrib/gendersub.py | 4 ++-- evennia/contrib/ingame_python/tests.py | 12 +++++------ evennia/contrib/ingame_python/typeclasses.py | 18 ++++++++--------- evennia/contrib/rpsystem.py | 6 +++--- evennia/contrib/simpledoor.py | 6 +++--- evennia/contrib/tests.py | 14 ++++++------- evennia/contrib/turnbattle.py | 2 +- evennia/contrib/turnbattle/tb_basic.py | 2 +- evennia/contrib/turnbattle/tb_equip.py | 2 +- evennia/contrib/tutorial_world/objects.py | 16 +++++++-------- evennia/contrib/tutorial_world/rooms.py | 12 +++++------ evennia/contrib/unixcommand.py | 8 ++++---- evennia/game_template/commands/command.py | 2 +- .../game_template/commands/default_cmdsets.py | 8 ++++---- evennia/objects/admin.py | 6 +++--- evennia/objects/objects.py | 10 +++++----- evennia/server/portal/portalsessionhandler.py | 2 +- evennia/server/portal/ssh.py | 2 +- evennia/server/portal/ssl.py | 2 +- evennia/server/portal/telnet.py | 6 +++--- evennia/server/portal/webclient_ajax.py | 4 ++-- evennia/server/sessionhandler.py | 10 +++++----- evennia/server/tests.py | 2 +- evennia/typeclasses/admin.py | 14 ++++++------- evennia/typeclasses/attributes.py | 12 +++++------ evennia/typeclasses/managers.py | 20 +++++++++---------- evennia/typeclasses/models.py | 4 ++-- evennia/utils/ansi.py | 6 +++--- evennia/utils/dbserialize.py | 10 +++++----- evennia/utils/idmapper/manager.py | 2 +- evennia/utils/idmapper/models.py | 12 +++++------ evennia/utils/idmapper/tests.py | 2 +- evennia/utils/inlinefuncs.py | 4 ++-- evennia/utils/picklefield.py | 10 +++++----- evennia/utils/test_resources.py | 2 +- evennia/utils/utils.py | 8 ++++---- 52 files changed, 164 insertions(+), 164 deletions(-) diff --git a/evennia/accounts/accounts.py b/evennia/accounts/accounts.py index aa68524383..277cd64575 100644 --- a/evennia/accounts/accounts.py +++ b/evennia/accounts/accounts.py @@ -381,7 +381,7 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)): self.attributes.clear() self.nicks.clear() self.aliases.clear() - super(DefaultAccount, self).delete(*args, **kwargs) + super().delete(*args, **kwargs) # methods inherited from database model def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs): @@ -529,7 +529,7 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)): result (bool): Result of access check. """ - result = super(DefaultAccount, self).access(accessing_obj, access_type=access_type, + result = super().access(accessing_obj, access_type=access_type, default=default, no_superuser_bypass=no_superuser_bypass) self.at_access(result, accessing_obj, access_type, **kwargs) return result @@ -979,7 +979,7 @@ class DefaultGuest(DefaultAccount): We repeat the functionality of `at_disconnect()` here just to be on the safe side. """ - super(DefaultGuest, self).at_server_shutdown() + super().at_server_shutdown() characters = self.db._playable_characters for character in characters: if character: @@ -995,7 +995,7 @@ class DefaultGuest(DefaultAccount): overriding the call (unused by default). """ - super(DefaultGuest, self).at_post_disconnect() + super().at_post_disconnect() characters = self.db._playable_characters for character in characters: if character: diff --git a/evennia/accounts/bots.py b/evennia/accounts/bots.py index f772e7c4d6..6d605ad6bc 100644 --- a/evennia/accounts/bots.py +++ b/evennia/accounts/bots.py @@ -118,14 +118,14 @@ class Bot(DefaultAccount): Evennia -> outgoing protocol """ - super(Bot, self).msg(text=text, from_obj=from_obj, session=session, options=options, **kwargs) + super().msg(text=text, from_obj=from_obj, session=session, options=options, **kwargs) def execute_cmd(self, raw_string, session=None): """ Incoming protocol -> Evennia """ - super(Bot, self).msg(raw_string, session=session) + super().msg(raw_string, session=session) def at_server_shutdown(self): """ @@ -226,7 +226,7 @@ class IRCBot(Bot): if not hasattr(self, "_nicklist_callers"): self._nicklist_callers = [] self._nicklist_callers.append(caller) - super(IRCBot, self).msg(request_nicklist="") + super().msg(request_nicklist="") return def ping(self, caller): @@ -240,7 +240,7 @@ class IRCBot(Bot): if not hasattr(self, "_ping_callers"): self._ping_callers = [] self._ping_callers.append(caller) - super(IRCBot, self).msg(ping="") + super().msg(ping="") def reconnect(self): """ @@ -248,7 +248,7 @@ class IRCBot(Bot): having to destroy/recreate the bot "account". """ - super(IRCBot, self).msg(reconnect="") + super().msg(reconnect="") def msg(self, text=None, **kwargs): """ @@ -270,7 +270,7 @@ class IRCBot(Bot): self.ndb.ev_channel = self.db.ev_channel if "from_channel" in options and text and self.ndb.ev_channel.dbid == options["from_channel"]: if not from_obj or from_obj != [self]: - super(IRCBot, self).msg(channel=text) + super().msg(channel=text) def execute_cmd(self, session=None, txt=None, **kwargs): """ @@ -336,7 +336,7 @@ class IRCBot(Bot): text = "This is an Evennia IRC bot connecting from '%s'." % settings.SERVERNAME else: text = "I understand 'who' and 'about'." - super(IRCBot, self).msg(privmsg=((text,), {"user": user})) + super().msg(privmsg=((text,), {"user": user})) else: # something to send to the main channel if kwargs["type"] == "action": diff --git a/evennia/commands/cmdset.py b/evennia/commands/cmdset.py index c363f87f80..c170d40a36 100644 --- a/evennia/commands/cmdset.py +++ b/evennia/commands/cmdset.py @@ -54,7 +54,7 @@ class _CmdSetMeta(type): if not isinstance(cls.key_mergetypes, dict): cls.key_mergetypes = {} - super(_CmdSetMeta, cls).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) class CmdSet(with_metaclass(_CmdSetMeta, object)): diff --git a/evennia/commands/command.py b/evennia/commands/command.py index 094934a02e..ab4fb90438 100644 --- a/evennia/commands/command.py +++ b/evennia/commands/command.py @@ -82,7 +82,7 @@ class CommandMeta(type): """ def __init__(cls, *args, **kwargs): _init_command(cls, **kwargs) - super(CommandMeta, cls).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # The Command class is the basic unit of an Evennia command; when # defining new commands, the admin subclass this class and diff --git a/evennia/commands/default/account.py b/evennia/commands/default/account.py index ecde6d8b32..d09ab08b65 100644 --- a/evennia/commands/default/account.py +++ b/evennia/commands/default/account.py @@ -46,7 +46,7 @@ class MuxAccountLookCommand(COMMAND_DEFAULT_CLASS): def parse(self): """Custom parsing""" - super(MuxAccountLookCommand, self).parse() + super().parse() if _MULTISESSION_MODE < 2: # only one character allowed - not used in this mode diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index 5b19f2006b..581a7f7561 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -71,7 +71,7 @@ class ObjManipCommand(COMMAND_DEFAULT_CLASS): the cases, see the module doc. """ # get all the normal parsing done (switches etc) - super(ObjManipCommand, self).parse() + super().parse() obj_defs = ([], []) # stores left- and right-hand side of '=' obj_attrs = ([], []) # " @@ -1079,7 +1079,7 @@ class CmdUnLink(CmdLink): self.rhs = "" # call the @link functionality - super(CmdUnLink, self).func() + super().func() class CmdSetHome(CmdLink): diff --git a/evennia/commands/default/muxcommand.py b/evennia/commands/default/muxcommand.py index 5d8d4b2890..67a61aad60 100644 --- a/evennia/commands/default/muxcommand.py +++ b/evennia/commands/default/muxcommand.py @@ -30,7 +30,7 @@ class MuxCommand(Command): We just show it here for completeness - we are satisfied using the default check in Command. """ - return super(MuxCommand, self).has_perm(srcobj) + return super().has_perm(srcobj) def at_pre_cmd(self): """ @@ -197,7 +197,7 @@ class MuxAccountCommand(MuxCommand): """ We run the parent parser as usual, then fix the result """ - super(MuxAccountCommand, self).parse() + super().parse() if utils.inherits_from(self.caller, "evennia.objects.objects.DefaultObject"): # caller is an Object/Character diff --git a/evennia/commands/default/tests.py b/evennia/commands/default/tests.py index 7d79e6ad5c..fee9d1bd3d 100644 --- a/evennia/commands/default/tests.py +++ b/evennia/commands/default/tests.py @@ -369,7 +369,7 @@ class TestBuilding(CommandTest): class TestComms(CommandTest): def setUp(self): - super(CommandTest, self).setUp() + super().setUp() self.call(comms.CmdChannelCreate(), "testchan;test=Test Channel", "Created channel testchan and connected to it.", receiver=self.account) def test_toggle_com(self): diff --git a/evennia/commands/tests.py b/evennia/commands/tests.py index 0e465e377b..5df671dbb7 100644 --- a/evennia/commands/tests.py +++ b/evennia/commands/tests.py @@ -14,7 +14,7 @@ class _CmdA(Command): key = "A" def __init__(self, cmdset, *args, **kwargs): - super(_CmdA, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.from_cmdset = cmdset @@ -22,7 +22,7 @@ class _CmdB(Command): key = "B" def __init__(self, cmdset, *args, **kwargs): - super(_CmdB, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.from_cmdset = cmdset @@ -30,7 +30,7 @@ class _CmdC(Command): key = "C" def __init__(self, cmdset, *args, **kwargs): - super(_CmdC, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.from_cmdset = cmdset @@ -38,7 +38,7 @@ class _CmdD(Command): key = "D" def __init__(self, cmdset, *args, **kwargs): - super(_CmdD, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.from_cmdset = cmdset @@ -85,7 +85,7 @@ class TestCmdSetMergers(TestCase): "Test merging of cmdsets" def setUp(self): - super(TestCmdSetMergers, self).setUp() + super().setUp() self.cmdset_a = _CmdSetA() self.cmdset_b = _CmdSetB() self.cmdset_c = _CmdSetC() @@ -272,7 +272,7 @@ class TestGetAndMergeCmdSets(TwistedTestCase, EvenniaTest): "Test the cmdhandler.get_and_merge_cmdsets function." def setUp(self): - super(TestGetAndMergeCmdSets, self).setUp() + super().setUp() self.cmdset_a = _CmdSetA() self.cmdset_b = _CmdSetB() self.cmdset_c = _CmdSetC() diff --git a/evennia/comms/admin.py b/evennia/comms/admin.py index 29a02507c1..f9ef82baa9 100644 --- a/evennia/comms/admin.py +++ b/evennia/comms/admin.py @@ -94,7 +94,7 @@ class ChannelAdmin(admin.ModelAdmin): from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse return HttpResponseRedirect(reverse("admin:comms_channeldb_change", args=[obj.id])) - return super(ChannelAdmin, self).response_add(request, obj, post_url_continue) + return super().response_add(request, obj, post_url_continue) admin.site.register(ChannelDB, ChannelAdmin) diff --git a/evennia/comms/comms.py b/evennia/comms/comms.py index 56cd054720..47f72cf205 100644 --- a/evennia/comms/comms.py +++ b/evennia/comms/comms.py @@ -220,7 +220,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): """ self.attributes.clear() self.aliases.clear() - super(DefaultChannel, self).delete() + super().delete() from evennia.comms.channelhandler import CHANNELHANDLER CHANNELHANDLER.update() diff --git a/evennia/contrib/chargen.py b/evennia/contrib/chargen.py index 19689e42b5..9bbeded6e0 100644 --- a/evennia/contrib/chargen.py +++ b/evennia/contrib/chargen.py @@ -116,7 +116,7 @@ class CmdOOCLook(default_cmds.CmdLook): # not ooc mode - leave back to normal look # we have to put this back for normal look to work. self.caller = self.character - super(CmdOOCLook, self).func() + super().func() class CmdOOCCharacterCreate(Command): diff --git a/evennia/contrib/clothing.py b/evennia/contrib/clothing.py index dd61c5f3db..608fa0aa05 100644 --- a/evennia/contrib/clothing.py +++ b/evennia/contrib/clothing.py @@ -684,7 +684,7 @@ class ClothedCharacterCmdSet(default_cmds.CharacterCmdSet): """ Populates the cmdset """ - super(ClothedCharacterCmdSet, self).at_cmdset_creation() + super().at_cmdset_creation() # # any commands you add below will overload the default ones. # diff --git a/evennia/contrib/egi_client/service.py b/evennia/contrib/egi_client/service.py index 8f8ce2bbb9..c18e898776 100644 --- a/evennia/contrib/egi_client/service.py +++ b/evennia/contrib/egi_client/service.py @@ -26,7 +26,7 @@ class EvenniaGameIndexService(Service): self.loop = LoopingCall(self.client.send_game_details) def startService(self): - super(EvenniaGameIndexService, self).startService() + super().startService() # TODO: Check to make sure that the client is configured. # Start the loop, but only after a short delay. This allows the # portal and the server time to sync up as far as total player counts. @@ -38,7 +38,7 @@ class EvenniaGameIndexService(Service): if self.running == 0: # @reload errors if we've stopped this service. return - super(EvenniaGameIndexService, self).stopService() + super().stopService() if self.loop.running: self.loop.stop() diff --git a/evennia/contrib/email_login.py b/evennia/contrib/email_login.py index c98bf4aa88..aeb34cd726 100644 --- a/evennia/contrib/email_login.py +++ b/evennia/contrib/email_login.py @@ -138,7 +138,7 @@ class CmdUnconnectedCreate(MuxCommand): name enclosed in quotes: connect "Long name with many words" my@myserv.com mypassw """ - super(CmdUnconnectedCreate, self).parse() + super().parse() self.accountinfo = [] if len(self.arglist) < 3: diff --git a/evennia/contrib/extended_room.py b/evennia/contrib/extended_room.py index 7204fa752e..21ceb8bba9 100644 --- a/evennia/contrib/extended_room.py +++ b/evennia/contrib/extended_room.py @@ -264,7 +264,7 @@ class ExtendedRoom(DefaultRoom): # and re-save the description again. self.db.desc = self.replace_timeslots(self.db.raw_desc, curr_timeslot) # run the normal return_appearance method, now that desc is updated. - return super(ExtendedRoom, self).return_appearance(looker) + return super().return_appearance(looker) # Custom Look command supporting Room details. Add this to diff --git a/evennia/contrib/gendersub.py b/evennia/contrib/gendersub.py index 205f9a991d..2aa4dc92a5 100644 --- a/evennia/contrib/gendersub.py +++ b/evennia/contrib/gendersub.py @@ -92,7 +92,7 @@ class GenderCharacter(DefaultCharacter): """ Called once when the object is created. """ - super(GenderCharacter, self).at_object_creation() + super().at_object_creation() self.db.gender = "ambiguous" def _get_pronoun(self, regex_match): @@ -139,4 +139,4 @@ class GenderCharacter(DefaultCharacter): text = _RE_GENDER_PRONOUN.sub(self._get_pronoun, text) except TypeError: pass - super(GenderCharacter, self).msg(text, from_obj=from_obj, session=session, **kwargs) + super().msg(text, from_obj=from_obj, session=session, **kwargs) diff --git a/evennia/contrib/ingame_python/tests.py b/evennia/contrib/ingame_python/tests.py index 15a8a45dad..5f6fe6ec50 100644 --- a/evennia/contrib/ingame_python/tests.py +++ b/evennia/contrib/ingame_python/tests.py @@ -30,7 +30,7 @@ class TestEventHandler(EvenniaTest): def setUp(self): """Create the event handler.""" - super(TestEventHandler, self).setUp() + super().setUp() self.handler = create_script( "evennia.contrib.ingame_python.scripts.EventHandler") @@ -51,7 +51,7 @@ class TestEventHandler(EvenniaTest): OLD_EVENTS.update(self.handler.ndb.events) self.handler.stop() CallbackHandler.script = None - super(TestEventHandler, self).tearDown() + super().tearDown() def test_start(self): """Simply make sure the handler runs with proper initial values.""" @@ -248,7 +248,7 @@ class TestCmdCallback(CommandTest): def setUp(self): """Create the callback handler.""" - super(TestCmdCallback, self).setUp() + super().setUp() self.handler = create_script( "evennia.contrib.ingame_python.scripts.EventHandler") @@ -273,7 +273,7 @@ class TestCmdCallback(CommandTest): script.stop() CallbackHandler.script = None - super(TestCmdCallback, self).tearDown() + super().tearDown() def test_list(self): """Test listing callbacks with different rights.""" @@ -413,7 +413,7 @@ class TestDefaultCallbacks(CommandTest): def setUp(self): """Create the callback handler.""" - super(TestDefaultCallbacks, self).setUp() + super().setUp() self.handler = create_script( "evennia.contrib.ingame_python.scripts.EventHandler") @@ -434,7 +434,7 @@ class TestDefaultCallbacks(CommandTest): OLD_EVENTS.update(self.handler.ndb.events) self.handler.stop() CallbackHandler.script = None - super(TestDefaultCallbacks, self).tearDown() + super().tearDown() def test_exit(self): """Test the callbacks of an exit.""" diff --git a/evennia/contrib/ingame_python/typeclasses.py b/evennia/contrib/ingame_python/typeclasses.py index 33729bef66..31b758eeb3 100644 --- a/evennia/contrib/ingame_python/typeclasses.py +++ b/evennia/contrib/ingame_python/typeclasses.py @@ -223,7 +223,7 @@ class EventCharacter(DefaultCharacter): if not string: return - super(EventCharacter, self).announce_move_from(destination, msg=string, mapping=mapping) + super().announce_move_from(destination, msg=string, mapping=mapping) def announce_move_to(self, source_location, msg=None, mapping=None): """ @@ -278,7 +278,7 @@ class EventCharacter(DefaultCharacter): if not string: return - super(EventCharacter, self).announce_move_to(source_location, msg=string, mapping=mapping) + super().announce_move_to(source_location, msg=string, mapping=mapping) def at_before_move(self, destination): """ @@ -328,7 +328,7 @@ class EventCharacter(DefaultCharacter): source_location (Object): Wwhere we came from. This may be `None`. """ - super(EventCharacter, self).at_after_move(source_location) + super().at_after_move(source_location) origin = source_location destination = self.location @@ -367,7 +367,7 @@ class EventCharacter(DefaultCharacter): puppeting this Object. """ - super(EventCharacter, self).at_post_puppet() + super().at_post_puppet() self.callbacks.call("puppeted", self) @@ -395,7 +395,7 @@ class EventCharacter(DefaultCharacter): if location and isinstance(location, DefaultRoom): location.callbacks.call("unpuppeted_in", self, location) - super(EventCharacter, self).at_pre_unpuppet() + super().at_pre_unpuppet() def at_before_say(self, message, **kwargs): """ @@ -482,7 +482,7 @@ class EventCharacter(DefaultCharacter): """ - super(EventCharacter, self).at_say(message, **kwargs) + super().at_say(message, **kwargs) location = getattr(self, "location", None) location = location if location and inherits_from(location, "evennia.objects.objects.DefaultRoom") else None @@ -624,7 +624,7 @@ class EventExit(DefaultExit): if not allow: return - super(EventExit, self).at_traverse(traversing_object, target_location) + super().at_traverse(traversing_object, target_location) # After traversing if is_character: @@ -703,7 +703,7 @@ class EventObject(DefaultObject): permissions for that. """ - super(EventObject, self).at_get(getter) + super().at_get(getter) self.callbacks.call("get", getter, self) def at_drop(self, dropper): @@ -719,7 +719,7 @@ class EventObject(DefaultObject): permissions from that. """ - super(EventObject, self).at_drop(dropper) + super().at_drop(dropper) self.callbacks.call("drop", dropper, self) diff --git a/evennia/contrib/rpsystem.py b/evennia/contrib/rpsystem.py index e3ea6b84e4..574e6b07ed 100644 --- a/evennia/contrib/rpsystem.py +++ b/evennia/contrib/rpsystem.py @@ -76,7 +76,7 @@ Verbose Installation Instructions: Change "class Character(DefaultCharacter):" to `class Character(ContribRPCharacter):` If you have any overriden calls in `at_object_creation(self)`: - Add `super(Character,self).at_object_creation()` as the top line. + Add `super().at_object_creation()` as the top line. 2. In `typeclasses/rooms.py`: Import the `ContribRPRoom` class: `from evennia.contrib.rpsystem import ContribRPRoom` @@ -1139,7 +1139,7 @@ class ContribRPObject(DefaultObject): """ Called at initial creation. """ - super(ContribRPObject, self).at_object_creation + super().at_object_creation() # emoting/recog data self.db.pose = "" @@ -1423,7 +1423,7 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject): """ Called at initial creation. """ - super(ContribRPCharacter, self).at_object_creation() + super().at_object_creation() self.db._sdesc = "" self.db._sdesc_regex = "" diff --git a/evennia/contrib/simpledoor.py b/evennia/contrib/simpledoor.py index d43d211fa3..991c089c82 100644 --- a/evennia/contrib/simpledoor.py +++ b/evennia/contrib/simpledoor.py @@ -77,8 +77,8 @@ class SimpleDoor(DefaultExit): """ # we have to be careful to avoid a delete-loop. if self.db.return_exit: - super(SimpleDoor, self.db.return_exit).delete() - super(SimpleDoor, self).delete() + super().delete() + super().delete() return True def at_failed_traverse(self, traversing_object): @@ -103,7 +103,7 @@ class CmdOpen(default_cmds.CmdOpen): Simple wrapper for the default CmdOpen.create_exit """ # create a new exit as normal - new_exit = super(CmdOpen, self).create_exit(exit_name, location, destination, + new_exit = super().create_exit(exit_name, location, destination, exit_aliases=exit_aliases, typeclass=typeclass) if hasattr(self, "return_exit_already_created"): # we don't create a return exit if it was already created (because diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index d3534d03cf..b97fb24922 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -25,7 +25,7 @@ text = "Automated testing is advantageous for a number of reasons:" \ class TestLanguage(EvenniaTest): def setUp(self): - super(TestLanguage, self).setUp() + super().setUp() rplanguage.add_language(key="testlang", word_length_variance=1, noun_prefix="bara", @@ -35,7 +35,7 @@ class TestLanguage(EvenniaTest): force=True) def tearDown(self): - super(TestLanguage, self).tearDown() + super().tearDown() rplanguage._LANGUAGE_HANDLER.delete() rplanguage._LANGUAGE_HANDLER = None @@ -79,7 +79,7 @@ emote = "With a flair, /me looks at /first and /colliding sdesc-guy. She says \" class TestRPSystem(EvenniaTest): def setUp(self): - super(TestRPSystem, self).setUp() + super().setUp() self.room = create_object(rpsystem.ContribRPRoom, key="Location") self.speaker = create_object(rpsystem.ContribRPCharacter, key="Sender", location=self.room) self.receiver1 = create_object(rpsystem.ContribRPCharacter, key="Receiver1", location=self.room) @@ -197,7 +197,7 @@ class TestExtendedRoom(CommandTest): settings.TIME_ZONE = "UTC" def setUp(self): - super(TestExtendedRoom, self).setUp() + super().setUp() self.room1.ndb.last_timeslot = "afternoon" self.room1.ndb.last_season = "winter" self.room1.db.details = {'testdetail': self.DETAIL_DESC} @@ -244,7 +244,7 @@ from evennia.contrib import barter class TestBarter(CommandTest): def setUp(self): - super(TestBarter, self).setUp() + super().setUp() self.tradeitem1 = create_object(key="TradeItem1", location=self.char1) self.tradeitem2 = create_object(key="TradeItem2", location=self.char1) self.tradeitem3 = create_object(key="TradeItem3", location=self.char2) @@ -331,7 +331,7 @@ from evennia import DefaultCharacter class TestWilderness(EvenniaTest): def setUp(self): - super(TestWilderness, self).setUp() + super().setUp() self.char1 = create_object(DefaultCharacter, key="char1") self.char2 = create_object(DefaultCharacter, key="char2") @@ -564,7 +564,7 @@ def _testcallback(): class TestCustomGameTime(EvenniaTest): def setUp(self): - super(TestCustomGameTime, self).setUp() + super().setUp() gametime.gametime = Mock(return_value=2975000898.46) # does not seem to work def tearDown(self): diff --git a/evennia/contrib/turnbattle.py b/evennia/contrib/turnbattle.py index 692656bc3a..90fccea781 100644 --- a/evennia/contrib/turnbattle.py +++ b/evennia/contrib/turnbattle.py @@ -541,7 +541,7 @@ class CmdCombatHelp(CmdHelp): "|wPass:|n Pass your turn without further action.|/" + "|wDisengage:|n End your turn and attempt to end combat.|/") else: - super(CmdCombatHelp, self).func() # Call the default help command + super().func() # Call the default help command class BattleCmdSet(default_cmds.CharacterCmdSet): diff --git a/evennia/contrib/turnbattle/tb_basic.py b/evennia/contrib/turnbattle/tb_basic.py index 70c81debae..f78ddf38c8 100644 --- a/evennia/contrib/turnbattle/tb_basic.py +++ b/evennia/contrib/turnbattle/tb_basic.py @@ -541,7 +541,7 @@ class CmdCombatHelp(CmdHelp): "|wPass:|n Pass your turn without further action.|/" + "|wDisengage:|n End your turn and attempt to end combat.|/") else: - super(CmdCombatHelp, self).func() # Call the default help command + super().func() # Call the default help command class BattleCmdSet(default_cmds.CharacterCmdSet): diff --git a/evennia/contrib/turnbattle/tb_equip.py b/evennia/contrib/turnbattle/tb_equip.py index 7d9ea58442..1b13c8db13 100644 --- a/evennia/contrib/turnbattle/tb_equip.py +++ b/evennia/contrib/turnbattle/tb_equip.py @@ -658,7 +658,7 @@ class CmdCombatHelp(CmdHelp): "|wPass:|n Pass your turn without further action.|/" + "|wDisengage:|n End your turn and attempt to end combat.|/") else: - super(CmdCombatHelp, self).func() # Call the default help command + super().func() # Call the default help command class CmdWield(Command): """ diff --git a/evennia/contrib/tutorial_world/objects.py b/evennia/contrib/tutorial_world/objects.py index 1e836d5bb1..779f37aba4 100644 --- a/evennia/contrib/tutorial_world/objects.py +++ b/evennia/contrib/tutorial_world/objects.py @@ -51,7 +51,7 @@ class TutorialObject(DefaultObject): def at_object_creation(self): """Called when the object is first created.""" - super(TutorialObject, self).at_object_creation() + super().at_object_creation() self.db.tutorial_info = "No tutorial info is available for this object." def reset(self): @@ -124,7 +124,7 @@ class Readable(TutorialObject): Called when object is created. We make sure to set the needed Attribute and add the readable cmdset. """ - super(Readable, self).at_object_creation() + super().at_object_creation() self.db.tutorial_info = "This is an object with a 'read' command defined in a command set on itself." self.db.readable_text = "There is no text written on %s." % self.key # define a command on the object. @@ -222,7 +222,7 @@ class Obelisk(TutorialObject): def at_object_creation(self): """Called when object is created.""" - super(Obelisk, self).at_object_creation() + super().at_object_creation() self.db.tutorial_info = "This object changes its desc randomly, and makes sure to remember which one you saw." self.db.puzzle_descs = ["You see a normal stone slab"] # make sure this can never be picked up @@ -246,7 +246,7 @@ class Obelisk(TutorialObject): caller.db.puzzle_clue = clueindex # call the parent function as normal (this will use # the new desc Attribute we just set) - return super(Obelisk, self).return_appearance(caller) + return super().return_appearance(caller) # ------------------------------------------------------------- @@ -320,7 +320,7 @@ class LightSource(TutorialObject): def at_object_creation(self): """Called when object is first created.""" - super(LightSource, self).at_object_creation() + super().at_object_creation() self.db.tutorial_info = "This object can be lit to create light. It has a timeout for how long it burns." self.db.is_giving_light = False self.db.burntime = 60 * 3 # 3 minutes @@ -602,7 +602,7 @@ class CrumblingWall(TutorialObject, DefaultExit): def at_object_creation(self): """called when the object is first created.""" - super(CrumblingWall, self).at_object_creation() + super().at_object_creation() self.aliases.add(["secret passage", "passage", "crack", "opening", "secret door"]) @@ -694,7 +694,7 @@ class CrumblingWall(TutorialObject, DefaultExit): self.db.desc = "".join(result) # call the parent to continue execution (will use the desc we just set) - return super(CrumblingWall, self).return_appearance(caller) + return super().return_appearance(caller) def at_after_traverse(self, traverser, source_location): """ @@ -863,7 +863,7 @@ class Weapon(TutorialObject): def at_object_creation(self): """Called at first creation of the object""" - super(Weapon, self).at_object_creation() + super().at_object_creation() self.db.hit = 0.4 # hit chance self.db.parry = 0.8 # parry chance self.db.damage = 1.0 diff --git a/evennia/contrib/tutorial_world/rooms.py b/evennia/contrib/tutorial_world/rooms.py index e780124609..5880e9daf5 100644 --- a/evennia/contrib/tutorial_world/rooms.py +++ b/evennia/contrib/tutorial_world/rooms.py @@ -311,7 +311,7 @@ class WeatherRoom(TutorialRoom): the ticking of the room; the TickerHandler works fine for simple things like this though. """ - super(WeatherRoom, self).at_object_creation() + super().at_object_creation() # subscribe ourselves to a ticker to repeatedly call the hook # "update_weather" on this object. The interval is randomized # so as to not have all weather rooms update at the same time. @@ -362,7 +362,7 @@ class IntroRoom(TutorialRoom): """ Called when the room is first created. """ - super(IntroRoom, self).at_object_creation() + super().at_object_creation() self.db.tutorial_info = "The first room of the tutorial. " \ "This assigns the health Attribute to "\ "the account." @@ -633,7 +633,7 @@ class BridgeRoom(WeatherRoom): """Setups the room""" # this will start the weather room's ticker and tell # it to call update_weather regularly. - super(BridgeRoom, self).at_object_creation() + super().at_object_creation() # this identifies the exits from the room (should be the command # needed to leave through that exit). These are defaults, but you # could of course also change them after the room has been created. @@ -836,7 +836,7 @@ class DarkRoom(TutorialRoom): """ Called when object is first created. """ - super(DarkRoom, self).at_object_creation() + super().at_object_creation() self.db.tutorial_info = "This is a room with custom command sets on itself." # the room starts dark. self.db.is_lit = False @@ -950,7 +950,7 @@ class TeleportRoom(TutorialRoom): def at_object_creation(self): """Called at first creation""" - super(TeleportRoom, self).at_object_creation() + super().at_object_creation() # what character.db.puzzle_clue must be set to, to avoid teleportation. self.db.puzzle_value = 1 # target of successful teleportation. Can be a dbref or a @@ -1016,7 +1016,7 @@ class OutroRoom(TutorialRoom): """ Called when the room is first created. """ - super(OutroRoom, self).at_object_creation() + super().at_object_creation() self.db.tutorial_info = "The last room of the tutorial. " \ "This cleans up all temporary Attributes " \ "the tutorial may have assigned to the "\ diff --git a/evennia/contrib/unixcommand.py b/evennia/contrib/unixcommand.py index 8ba538c57c..0b63c288ca 100644 --- a/evennia/contrib/unixcommand.py +++ b/evennia/contrib/unixcommand.py @@ -110,7 +110,7 @@ class UnixCommandParser(argparse.ArgumentParser): """ prog = prog or command.key - super(UnixCommandParser, self).__init__( + super().__init__( prog=prog, description=description, conflict_handler='resolve', add_help=False, **kwargs) self.command = command @@ -133,7 +133,7 @@ class UnixCommandParser(argparse.ArgumentParser): in order to avoid unintentional color codes. """ - return raw(super(UnixCommandParser, self).format_usage()) + return raw(super().format_usage()) def format_help(self): """Return the parser help, including its epilog. @@ -144,7 +144,7 @@ class UnixCommandParser(argparse.ArgumentParser): in the epilog (the command docstring) are supported. """ - autohelp = raw(super(UnixCommandParser, self).format_help()) + autohelp = raw(super().format_help()) return "\n" + autohelp + "\n" + self.post_help def print_usage(self, file=None): @@ -234,7 +234,7 @@ class UnixCommand(Command): overloading evential same-named class properties. """ - super(UnixCommand, self).__init__(**kwargs) + super().__init__(**kwargs) # Create the empty UnixCommandParser, inheriting argparse.ArgumentParser lines = dedent(self.__doc__.strip("\n")).splitlines() diff --git a/evennia/game_template/commands/command.py b/evennia/game_template/commands/command.py index 529a8450e4..c085025b8c 100644 --- a/evennia/game_template/commands/command.py +++ b/evennia/game_template/commands/command.py @@ -70,7 +70,7 @@ class Command(BaseCommand): # We just show it here for completeness - we # are satisfied using the default check in Command. # """ -# return super(MuxCommand, self).has_perm(srcobj) +# return super().has_perm(srcobj) # # def at_pre_cmd(self): # """ diff --git a/evennia/game_template/commands/default_cmdsets.py b/evennia/game_template/commands/default_cmdsets.py index a2e5ffbf4d..7633f14c4a 100644 --- a/evennia/game_template/commands/default_cmdsets.py +++ b/evennia/game_template/commands/default_cmdsets.py @@ -29,7 +29,7 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet): """ Populates the cmdset """ - super(CharacterCmdSet, self).at_cmdset_creation() + super().at_cmdset_creation() # # any commands you add below will overload the default ones. # @@ -48,7 +48,7 @@ class AccountCmdSet(default_cmds.AccountCmdSet): """ Populates the cmdset """ - super(AccountCmdSet, self).at_cmdset_creation() + super().at_cmdset_creation() # # any commands you add below will overload the default ones. # @@ -65,7 +65,7 @@ class UnloggedinCmdSet(default_cmds.UnloggedinCmdSet): """ Populates the cmdset """ - super(UnloggedinCmdSet, self).at_cmdset_creation() + super().at_cmdset_creation() # # any commands you add below will overload the default ones. # @@ -86,7 +86,7 @@ class SessionCmdSet(default_cmds.SessionCmdSet): As and example we just add the empty base `Command` object. It prints some info. """ - super(SessionCmdSet, self).at_cmdset_creation() + super().at_cmdset_creation() # # any commands you add below will overload the default ones. # diff --git a/evennia/objects/admin.py b/evennia/objects/admin.py index 007cfe21c8..7e1d9056d9 100644 --- a/evennia/objects/admin.py +++ b/evennia/objects/admin.py @@ -120,7 +120,7 @@ class ObjectDBAdmin(admin.ModelAdmin): """ if not obj: return self.add_fieldsets - return super(ObjectDBAdmin, self).get_fieldsets(request, obj) + return super().get_fieldsets(request, obj) def get_form(self, request, obj=None, **kwargs): """ @@ -138,7 +138,7 @@ class ObjectDBAdmin(admin.ModelAdmin): 'fields': flatten_fieldsets(self.add_fieldsets), }) defaults.update(kwargs) - return super(ObjectDBAdmin, self).get_form(request, obj, **defaults) + return super().get_form(request, obj, **defaults) def save_model(self, request, obj, form, change): """ @@ -166,7 +166,7 @@ class ObjectDBAdmin(admin.ModelAdmin): from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse return HttpResponseRedirect(reverse("admin:objects_objectdb_change", args=[obj.id])) - return super(ObjectDBAdmin, self).response_add(request, obj, post_url_continue) + return super().response_add(request, obj, post_url_continue) admin.site.register(ObjectDB, ObjectDBAdmin) diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index 23e65c31bf..a354764236 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -877,7 +877,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): self.location = None # this updates contents_cache for our location # Perform the deletion of the object - super(DefaultObject, self).delete() + super().delete() return True def access(self, accessing_obj, access_type='read', default=False, no_superuser_bypass=False, **kwargs): @@ -896,7 +896,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): Passed on to the at_access hook along with the result of the access check. """ - result = super(DefaultObject, self).access(accessing_obj, access_type=access_type, + result = super().access(accessing_obj, access_type=access_type, default=default, no_superuser_bypass=no_superuser_bypass) self.at_access(result, accessing_obj, access_type, **kwargs) return result @@ -1760,7 +1760,7 @@ class DefaultCharacter(DefaultObject): Character object works). """ - super(DefaultCharacter, self).basetype_setup() + super().basetype_setup() self.locks.add(";".join(["get:false()", # noone can pick up the character "call:false()"])) # no commands can be called on character from outside # add the default cmdset @@ -1874,7 +1874,7 @@ class DefaultRoom(DefaultObject): """ - super(DefaultRoom, self).basetype_setup() + super().basetype_setup() self.locks.add(";".join(["get:false()", "puppet:false()"])) # would be weird to puppet a room ... self.location = None @@ -1990,7 +1990,7 @@ class DefaultExit(DefaultObject): sure you include all the functionality in this method. """ - super(DefaultExit, self).basetype_setup() + super().basetype_setup() # setting default locks (overload these in at_object_creation() self.locks.add(";".join(["puppet:false()", # would be weird to puppet an exit ... diff --git a/evennia/server/portal/portalsessionhandler.py b/evennia/server/portal/portalsessionhandler.py index 032fdfc5bb..c06757b798 100644 --- a/evennia/server/portal/portalsessionhandler.py +++ b/evennia/server/portal/portalsessionhandler.py @@ -50,7 +50,7 @@ class PortalSessionHandler(SessionHandler): Init the handler """ - super(PortalSessionHandler, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.portal = None self.latest_sessid = 0 self.uptime = time.time() diff --git a/evennia/server/portal/ssh.py b/evennia/server/portal/ssh.py index 980839b624..34cc618e8f 100644 --- a/evennia/server/portal/ssh.py +++ b/evennia/server/portal/ssh.py @@ -338,7 +338,7 @@ class AccountDBPasswordChecker(object): """ self.factory = factory - super(AccountDBPasswordChecker, self).__init__() + super().__init__() def requestAvatarId(self, c): """ diff --git a/evennia/server/portal/ssl.py b/evennia/server/portal/ssl.py index 761df6766d..27703e43cf 100644 --- a/evennia/server/portal/ssl.py +++ b/evennia/server/portal/ssl.py @@ -52,7 +52,7 @@ class SSLProtocol(TelnetProtocol): """ def __init__(self, *args, **kwargs): - super(SSLProtocol, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.protocol_name = "ssl" diff --git a/evennia/server/portal/telnet.py b/evennia/server/portal/telnet.py index ec41bbfc8c..a75f9e275a 100644 --- a/evennia/server/portal/telnet.py +++ b/evennia/server/portal/telnet.py @@ -35,7 +35,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): def __init__(self, *args, **kwargs): self.protocol_name = "telnet" - super(TelnetProtocol, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def connectionMade(self): """ @@ -169,7 +169,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): self.mccp.no_mccp(option) return True else: - return super(TelnetProtocol, self).disableLocal(option) + return super().disableLocal(option) def connectionLost(self, reason): """ @@ -220,7 +220,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): def _write(self, data): """hook overloading the one used in plain telnet""" data = data.replace(b'\n', b'\r\n').replace(b'\r\r\n', b'\r\n') - super(TelnetProtocol, self)._write(mccp_compress(self, data)) + super()._write(mccp_compress(self, data)) def sendLine(self, line): """ diff --git a/evennia/server/portal/webclient_ajax.py b/evennia/server/portal/webclient_ajax.py index 02759b79fd..414f0e72e0 100644 --- a/evennia/server/portal/webclient_ajax.py +++ b/evennia/server/portal/webclient_ajax.py @@ -44,7 +44,7 @@ class LazyEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, Promise): return str(obj) - return super(LazyEncoder, self).default(obj) + return super().default(obj) def jsonify(obj): @@ -298,7 +298,7 @@ class AjaxWebClientSession(session.Session): def __init__(self, *args, **kwargs): self.protocol_name = "ajax/comet" - super(AjaxWebClientSession, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def get_client_session(self): """ diff --git a/evennia/server/sessionhandler.py b/evennia/server/sessionhandler.py index 89d6afde89..34030ade3c 100644 --- a/evennia/server/sessionhandler.py +++ b/evennia/server/sessionhandler.py @@ -104,22 +104,22 @@ class SessionHandler(dict): "Clean out None-sessions automatically." if None in self: del self[None] - return super(SessionHandler, self).__getitem__(key) + return super().__getitem__(key) def get(self, key, default=None): "Clean out None-sessions automatically." if None in self: del self[None] - return super(SessionHandler, self).get(key, default) + return super().get(key, default) def __setitem__(self, key, value): "Don't assign None sessions" if key is not None: - super(SessionHandler, self).__setitem__(key, value) + super().__setitem__(key, value) def __contains__(self, key): "None-keys are not accepted." - return False if key is None else super(SessionHandler, self).__contains__(key) + return False if key is None else super().__contains__(key) def get_sessions(self, include_unloggedin=False): """ @@ -267,7 +267,7 @@ class ServerSessionHandler(SessionHandler): Init the handler. """ - super(ServerSessionHandler, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.server = None self.server_data = {"servername": _SERVERNAME} diff --git a/evennia/server/tests.py b/evennia/server/tests.py index 7b39a2d7fd..db861573f8 100644 --- a/evennia/server/tests.py +++ b/evennia/server/tests.py @@ -45,4 +45,4 @@ class EvenniaTestSuiteRunner(DiscoverRunner): """ import evennia evennia._init() - return super(EvenniaTestSuiteRunner, self).build_suite(test_labels, extra_tests=extra_tests, **kwargs) + return super().build_suite(test_labels, extra_tests=extra_tests, **kwargs) diff --git a/evennia/typeclasses/admin.py b/evennia/typeclasses/admin.py index c5dd481f90..d6ef397343 100644 --- a/evennia/typeclasses/admin.py +++ b/evennia/typeclasses/admin.py @@ -48,7 +48,7 @@ class TagForm(forms.ModelForm): the corresponding tag fields. The initial data of the form fields will similarly be populated. """ - super(TagForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) tagkey = None tagcategory = None tagtype = None @@ -75,7 +75,7 @@ class TagForm(forms.ModelForm): we'll try to make sure that empty form fields will be None, rather than ''. """ # we are spoofing a tag for the Handler that will be called - # instance = super(TagForm, self).save(commit=False) + # instance = super().save(commit=False) instance = self.instance instance.tag_key = self.cleaned_data['tag_key'] instance.tag_category = self.cleaned_data['tag_category'] or None @@ -109,7 +109,7 @@ class TagFormSet(forms.BaseInlineFormSet): else: handler_name = "tags" return getattr(related, handler_name) - instances = super(TagFormSet, self).save(commit=False) + instances = super().save(commit=False) # self.deleted_objects is a list created when super of save is called, we'll remove those for obj in self.deleted_objects: handler = get_handler(obj) @@ -143,7 +143,7 @@ class TagInline(admin.TabularInline): a proxy isn't threadsafe, since it'd be the base class and would change if multiple people used the admin at the same time """ - formset = super(TagInline, self).get_formset(request, obj, **kwargs) + formset = super().get_formset(request, obj, **kwargs) class ProxyFormset(formset): pass @@ -190,7 +190,7 @@ class AttributeForm(forms.ModelForm): similarly be populated. """ - super(AttributeForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) attr_key = None attr_category = None attr_value = None @@ -255,7 +255,7 @@ class AttributeFormSet(forms.BaseInlineFormSet): else: handler_name = "attributes" return getattr(related, handler_name) - instances = super(AttributeFormSet, self).save(commit=False) + instances = super().save(commit=False) # self.deleted_objects is a list created when super of save is called, we'll remove those for obj in self.deleted_objects: handler = get_handler(obj) @@ -297,7 +297,7 @@ class AttributeInline(admin.TabularInline): a proxy isn't threadsafe, since it'd be the base class and would change if multiple people used the admin at the same time """ - formset = super(AttributeInline, self).get_formset(request, obj, **kwargs) + formset = super().get_formset(request, obj, **kwargs) class ProxyFormset(formset): pass diff --git a/evennia/typeclasses/attributes.py b/evennia/typeclasses/attributes.py index 1d3b820226..5bae73573d 100644 --- a/evennia/typeclasses/attributes.py +++ b/evennia/typeclasses/attributes.py @@ -800,7 +800,7 @@ class NickHandler(AttributeHandler): _attrtype = "nick" def __init__(self, *args, **kwargs): - super(NickHandler, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self._regex_cache = {} def has(self, key, category="inputline"): @@ -816,7 +816,7 @@ class NickHandler(AttributeHandler): is a list of booleans. """ - return super(NickHandler, self).has(key, category=category) + return super().has(key, category=category) def get(self, key=None, category="inputline", return_tuple=False, **kwargs): """ @@ -836,9 +836,9 @@ class NickHandler(AttributeHandler): """ if return_tuple or "return_obj" in kwargs: - return super(NickHandler, self).get(key=key, category=category, **kwargs) + return super().get(key=key, category=category, **kwargs) else: - retval = super(NickHandler, self).get(key=key, category=category, **kwargs) + retval = super().get(key=key, category=category, **kwargs) if retval: return retval[3] if isinstance(retval, tuple) else \ [tup[3] for tup in make_iter(retval)] @@ -861,7 +861,7 @@ class NickHandler(AttributeHandler): nick_regex, nick_template = initialize_nick_templates(key + " $1", replacement + " $1") else: nick_regex, nick_template = initialize_nick_templates(key, replacement) - super(NickHandler, self).add(key, (nick_regex, nick_template, key, replacement), + super().add(key, (nick_regex, nick_template, key, replacement), category=category, **kwargs) def remove(self, key, category="inputline", **kwargs): @@ -876,7 +876,7 @@ class NickHandler(AttributeHandler): kwargs (any, optional): These are passed on to `AttributeHandler.get`. """ - super(NickHandler, self).remove(key, category=category, **kwargs) + super().remove(key, category=category, **kwargs) def nickreplace(self, raw_string, categories=("inputline", "channel"), include_account=True): """ diff --git a/evennia/typeclasses/managers.py b/evennia/typeclasses/managers.py index 47293eaa59..7bd2a406ad 100644 --- a/evennia/typeclasses/managers.py +++ b/evennia/typeclasses/managers.py @@ -390,7 +390,7 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): the given dbref ranges. """ - retval = super(TypedObjectManager, self).all() + retval = super().all() if min_dbref is not None: retval = retval.filter(id__gte=self.dbref(min_dbref, reqhash=False)) if max_dbref is not None: @@ -545,7 +545,7 @@ class TypeclassManager(TypedObjectManager): """ kwargs.update({"db_typeclass_path": self.model.path}) - return super(TypeclassManager, self).get(**kwargs) + return super().get(**kwargs) def filter(self, *args, **kwargs): """ @@ -563,7 +563,7 @@ class TypeclassManager(TypedObjectManager): """ kwargs.update({"db_typeclass_path": self.model.path}) - return super(TypeclassManager, self).filter(*args, **kwargs) + return super().filter(*args, **kwargs) def all(self): """ @@ -573,7 +573,7 @@ class TypeclassManager(TypedObjectManager): objects (queryset): The objects found. """ - return super(TypeclassManager, self).all().filter(db_typeclass_path=self.model.path) + return super().all().filter(db_typeclass_path=self.model.path) def first(self): """ @@ -587,7 +587,7 @@ class TypeclassManager(TypedObjectManager): on the model base used. """ - return super(TypeclassManager, self).filter(db_typeclass_path=self.model.path).first() + return super().filter(db_typeclass_path=self.model.path).first() def last(self): """ @@ -601,7 +601,7 @@ class TypeclassManager(TypedObjectManager): on the model base used. """ - return super(TypeclassManager, self).filter(db_typeclass_path=self.model.path).last() + return super().filter(db_typeclass_path=self.model.path).last() def count(self): """ @@ -611,7 +611,7 @@ class TypeclassManager(TypedObjectManager): integer : Number of objects found. """ - return super(TypeclassManager, self).filter(db_typeclass_path=self.model.path).count() + return super().filter(db_typeclass_path=self.model.path).count() def _get_subclasses(self, cls): """ @@ -644,7 +644,7 @@ class TypeclassManager(TypedObjectManager): paths = [self.model.path] + ["%s.%s" % (cls.__module__, cls.__name__) for cls in self._get_subclasses(self.model)] kwargs.update({"db_typeclass_path__in": paths}) - return super(TypeclassManager, self).get(**kwargs) + return super().get(**kwargs) def filter_family(self, *args, **kwargs): """ @@ -665,7 +665,7 @@ class TypeclassManager(TypedObjectManager): paths = [self.model.path] + ["%s.%s" % (cls.__module__, cls.__name__) for cls in self._get_subclasses(self.model)] kwargs.update({"db_typeclass_path__in": paths}) - return super(TypeclassManager, self).filter(*args, **kwargs) + return super().filter(*args, **kwargs) def all_family(self): """ @@ -678,4 +678,4 @@ class TypeclassManager(TypedObjectManager): """ paths = [self.model.path] + ["%s.%s" % (cls.__module__, cls.__name__) for cls in self._get_subclasses(self.model)] - return super(TypeclassManager, self).all().filter(db_typeclass_path__in=paths) + return super().all().filter(db_typeclass_path__in=paths) diff --git a/evennia/typeclasses/models.py b/evennia/typeclasses/models.py index dbe48d7705..4dad9aa038 100644 --- a/evennia/typeclasses/models.py +++ b/evennia/typeclasses/models.py @@ -256,7 +256,7 @@ class TypedObject(SharedMemoryModel): """ typeclass_path = kwargs.pop("typeclass", None) - super(TypedObject, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.set_class_from_typeclass(typeclass_path=typeclass_path) # initialize all handlers in a lazy fashion @@ -604,7 +604,7 @@ class TypedObject(SharedMemoryModel): self.nicks.clear() # scrambling properties self.delete = self._deleted - super(TypedObject, self).delete() + super().delete() # # Attribute storage diff --git a/evennia/utils/ansi.py b/evennia/utils/ansi.py index e69845abb8..a63a11a2db 100644 --- a/evennia/utils/ansi.py +++ b/evennia/utils/ansi.py @@ -630,7 +630,7 @@ class ANSIMeta(type): for func_name in [ 'capitalize', 'translate', 'lower', 'upper', 'swapcase']: setattr(cls, func_name, _transform(func_name)) - super(ANSIMeta, cls).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) class ANSIString(with_metaclass(ANSIMeta, str)): @@ -708,7 +708,7 @@ class ANSIString(with_metaclass(ANSIMeta, str)): if not isinstance(string, str): string = string.decode('utf-8') - ansi_string = super(ANSIString, cls).__new__(ANSIString, to_str(clean_string)) + ansi_string = super().__new__(ANSIString, to_str(clean_string)) ansi_string._raw_string = string ansi_string._clean_string = clean_string ansi_string._code_indexes = code_indexes @@ -764,7 +764,7 @@ class ANSIString(with_metaclass(ANSIMeta, str)): """ self.parser = kwargs.pop('parser', ANSI_PARSER) - super(ANSIString, self).__init__() + super().__init__() if self._code_indexes is None: self._code_indexes, self._char_indexes = self._get_indexes() diff --git a/evennia/utils/dbserialize.py b/evennia/utils/dbserialize.py index dfc184d064..c4bc141a4d 100644 --- a/evennia/utils/dbserialize.py +++ b/evennia/utils/dbserialize.py @@ -233,7 +233,7 @@ class _SaverList(_SaverMutable, MutableSequence): """ def __init__(self, *args, **kwargs): - super(_SaverList, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self._data = list() @_save @@ -268,7 +268,7 @@ class _SaverDict(_SaverMutable, MutableMapping): """ def __init__(self, *args, **kwargs): - super(_SaverDict, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self._data = dict() def has_key(self, key): @@ -281,7 +281,7 @@ class _SaverSet(_SaverMutable, MutableSet): """ def __init__(self, *args, **kwargs): - super(_SaverSet, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self._data = set() def __contains__(self, value): @@ -302,7 +302,7 @@ class _SaverOrderedDict(_SaverMutable, MutableMapping): """ def __init__(self, *args, **kwargs): - super(_SaverOrderedDict, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self._data = OrderedDict() def has_key(self, key): @@ -315,7 +315,7 @@ class _SaverDeque(_SaverMutable): """ def __init__(self, *args, **kwargs): - super(_SaverDeque, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self._data = deque() @_save diff --git a/evennia/utils/idmapper/manager.py b/evennia/utils/idmapper/manager.py index 9053149e8a..57eb546b0e 100644 --- a/evennia/utils/idmapper/manager.py +++ b/evennia/utils/idmapper/manager.py @@ -33,5 +33,5 @@ class SharedMemoryManager(Manager): except Exception: pass if inst is None: - inst = super(SharedMemoryManager, self).get(*args, **kwargs) + inst = super().get(*args, **kwargs) return inst diff --git a/evennia/utils/idmapper/models.py b/evennia/utils/idmapper/models.py index 326c387f55..8da63663db 100644 --- a/evennia/utils/idmapper/models.py +++ b/evennia/utils/idmapper/models.py @@ -85,7 +85,7 @@ class SharedMemoryModelBase(ModelBase): if not hasattr(dbmodel, "__instance_cache__"): # we store __instance_cache__ only on the dbmodel base dbmodel.__instance_cache__ = {} - super(SharedMemoryModelBase, cls)._prepare() + super()._prepare() def __new__(cls, name, bases, attrs): """ @@ -203,7 +203,7 @@ class SharedMemoryModelBase(ModelBase): # makes sure not to overload manually created wrappers on the model create_wrapper(cls, fieldname, wrappername, editable=field.editable, foreignkey=foreignkey) - return super(SharedMemoryModelBase, cls).__new__(cls, name, bases, attrs) + return super().__new__(cls, name, bases, attrs) class SharedMemoryModel(with_metaclass(SharedMemoryModelBase, Model)): @@ -364,7 +364,7 @@ class SharedMemoryModel(with_metaclass(SharedMemoryModelBase, Model)): """ self.flush_from_cache() self._is_deleted = True - super(SharedMemoryModel, self).delete(*args, **kwargs) + super().delete(*args, **kwargs) def save(self, *args, **kwargs): """ @@ -390,11 +390,11 @@ class SharedMemoryModel(with_metaclass(SharedMemoryModelBase, Model)): if _IS_MAIN_THREAD: # in main thread - normal operation - super(SharedMemoryModel, self).save(*args, **kwargs) + super().save(*args, **kwargs) else: # in another thread; make sure to save in reactor thread def _save_callback(cls, *args, **kwargs): - super(SharedMemoryModel, cls).save(*args, **kwargs) + super().save(*args, **kwargs) callFromThread(_save_callback, self, *args, **kwargs) # update field-update hooks and eventual OOB watchers @@ -429,7 +429,7 @@ class WeakSharedMemoryModelBase(SharedMemoryModelBase): """ def _prepare(cls): - super(WeakSharedMemoryModelBase, cls)._prepare() + super()._prepare() cls.__dbclass__.__instance_cache__ = WeakValueDictionary() diff --git a/evennia/utils/idmapper/tests.py b/evennia/utils/idmapper/tests.py index 374eb0187a..36a612e710 100644 --- a/evennia/utils/idmapper/tests.py +++ b/evennia/utils/idmapper/tests.py @@ -31,7 +31,7 @@ class SharedMemorysTest(TestCase): # TODO: test for cross model relation (singleton to regular) def setUp(self): - super(SharedMemorysTest, self).setUp() + super().setUp() n = 0 category = Category.objects.create(name="Category %d" % (n,)) regcategory = RegularCategory.objects.create(name="Category %d" % (n,)) diff --git a/evennia/utils/inlinefuncs.py b/evennia/utils/inlinefuncs.py index 118f4fea5d..919c291c32 100644 --- a/evennia/utils/inlinefuncs.py +++ b/evennia/utils/inlinefuncs.py @@ -224,14 +224,14 @@ class ParseStack(list): """ def __init__(self, *args, **kwargs): - super(ParseStack, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # always start stack with the empty string list.append(self, "") # indicates if the top of the stack is a string or not self._string_last = True def __eq__(self, other): - return (super(ParseStack).__eq__(other) and + return (super().__eq__(other) and hasattr(other, "_string_last") and self._string_last == other._string_last) def __ne__(self, other): diff --git a/evennia/utils/picklefield.py b/evennia/utils/picklefield.py index 61ed452c19..d7fc256e85 100644 --- a/evennia/utils/picklefield.py +++ b/evennia/utils/picklefield.py @@ -151,7 +151,7 @@ class PickledFormField(CharField): def __init__(self, *args, **kwargs): # This needs to fall through to literal_eval. kwargs['required'] = False - super(PickledFormField, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def clean(self, value): try: @@ -184,7 +184,7 @@ class PickledObjectField(models.Field): def __init__(self, *args, **kwargs): self.compress = kwargs.pop('compress', False) self.protocol = kwargs.pop('protocol', DEFAULT_PROTOCOL) - super(PickledObjectField, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def get_default(self): """ @@ -203,7 +203,7 @@ class PickledObjectField(models.Field): return self.default() return self.default # If the field doesn't have a default, then we punt to models.Field. - return super(PickledObjectField, self).get_default() + return super().get_default() # def to_python(self, value): def from_db_value(self, value, *args): @@ -233,7 +233,7 @@ class PickledObjectField(models.Field): return PickledFormField(**kwargs) def pre_save(self, model_instance, add): - value = super(PickledObjectField, self).pre_save(model_instance, add) + value = super().pre_save(model_instance, add) return wrap_conflictual_object(value) def get_db_prep_value(self, value, connection=None, prepared=False): @@ -269,5 +269,5 @@ class PickledObjectField(models.Field): raise TypeError('Lookup type %s is not supported.' % lookup_type) # The Field model already calls get_db_prep_value before doing the # actual lookup, so all we need to do is limit the lookup types. - return super(PickledObjectField, self).get_db_prep_lookup( + return super().get_db_prep_lookup( lookup_type, value, connection=connection, prepared=prepared) diff --git a/evennia/utils/test_resources.py b/evennia/utils/test_resources.py index b4124b7219..2fdebaa85e 100644 --- a/evennia/utils/test_resources.py +++ b/evennia/utils/test_resources.py @@ -65,4 +65,4 @@ class EvenniaTest(TestCase): del SESSIONS[self.session.sessid] self.account.delete() self.account2.delete() - super(EvenniaTest, self).tearDown() + super().tearDown() diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index 832b8a01ed..9562253852 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -1807,13 +1807,13 @@ class LimitedSizeOrderedDict(OrderedDict): in FIFO order. If `False`, remove in FILO order. """ - super(LimitedSizeOrderedDict, self).__init__() + super().__init__() self.size_limit = kwargs.get("size_limit", None) self.filo = not kwargs.get("fifo", True) # FIFO inverse of FILO self._check_size() def __eq__(self, other): - ret = super(LimitedSizeOrderedDict, self).__eq__(other) + ret = super().__eq__(other) if ret: return (ret and hasattr(other, 'size_limit') and self.size_limit == other.size_limit and @@ -1830,11 +1830,11 @@ class LimitedSizeOrderedDict(OrderedDict): self.popitem(last=filo) def __setitem__(self, key, value): - super(LimitedSizeOrderedDict, self).__setitem__(key, value) + super().__setitem__(key, value) self._check_size() def update(self, *args, **kwargs): - super(LimitedSizeOrderedDict, self).update(*args, **kwargs) + super().update(*args, **kwargs) self._check_size() From 8c318c6d38e5b7b3ad2de7125f31c64d47b270f6 Mon Sep 17 00:00:00 2001 From: Ryan Stein Date: Thu, 2 Nov 2017 10:43:13 -0400 Subject: [PATCH 025/493] Configure Travis CI for Python 3. --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index d97c609759..6473f88745 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: python python: - - "2.7" + - "3.6" sudo: false -install: +install: - pip install -e . - pip install coveralls before_script: @@ -10,6 +10,6 @@ before_script: - cd dummy - evennia migrate script: - - coverage run --source=../evennia --omit=*/migrations/*,*/urls.py,*/test*.py,*.sh,*.txt,*.md,*.pyc,*.service ../bin/unix/evennia test evennia + - coverage run --source=../evennia --omit=*/migrations/*,*/urls.py,*/test*.py,*.sh,*.txt,*.md,*.pyc,*.service ../bin/unix/evennia test evennia after_success: - coveralls From f2e800ddf1ecd53ac6e6de50e891214da329ad59 Mon Sep 17 00:00:00 2001 From: Ryan Stein Date: Thu, 2 Nov 2017 12:46:33 -0400 Subject: [PATCH 026/493] Unwrap several `for` statements from 2to3 conversion process. --- bin/project_rename.py | 2 +- evennia/commands/cmdhandler.py | 2 +- evennia/commands/default/system.py | 4 ++-- evennia/contrib/custom_gametime.py | 4 ++-- evennia/contrib/ingame_python/callbackhandler.py | 2 +- evennia/contrib/ingame_python/scripts.py | 8 ++++---- evennia/contrib/ingame_python/utils.py | 2 +- evennia/contrib/tutorial_world/objects.py | 2 +- evennia/contrib/wilderness.py | 2 +- evennia/objects/objects.py | 2 +- evennia/scripts/taskhandler.py | 8 ++++---- evennia/scripts/tickerhandler.py | 4 ++-- evennia/server/evennia_launcher.py | 2 +- evennia/server/portal/mssp.py | 2 +- evennia/server/portal/portalsessionhandler.py | 4 ++-- evennia/server/session.py | 2 +- evennia/server/sessionhandler.py | 6 +++--- evennia/utils/batchprocessors.py | 2 +- evennia/utils/eveditor.py | 2 +- evennia/utils/evform.py | 6 +++--- evennia/utils/evtable.py | 4 ++-- evennia/utils/spawner.py | 2 +- 22 files changed, 37 insertions(+), 37 deletions(-) diff --git a/bin/project_rename.py b/bin/project_rename.py index 2d5bcb779e..f4c2d9c7ac 100644 --- a/bin/project_rename.py +++ b/bin/project_rename.py @@ -261,7 +261,7 @@ def rename_in_file(path, in_list, out_list, is_interactive): break elif ret == "a": # save result - for iline, renamed_line in list(renamed.items()): + for iline, renamed_line in renamed.items(): org_lines[iline] = renamed_line if FAKE_MODE: diff --git a/evennia/commands/cmdhandler.py b/evennia/commands/cmdhandler.py index b934932352..91ad60cc54 100644 --- a/evennia/commands/cmdhandler.py +++ b/evennia/commands/cmdhandler.py @@ -567,7 +567,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess returnValue(cmd) # assign custom kwargs to found cmd object - for key, val in list(kwargs.items()): + for key, val in kwargs.items(): setattr(cmd, key, val) _COMMAND_NESTING[called_by] += 1 diff --git a/evennia/commands/default/system.py b/evennia/commands/default/system.py index 99879c3b10..1d80f3578f 100644 --- a/evennia/commands/default/system.py +++ b/evennia/commands/default/system.py @@ -440,7 +440,7 @@ class CmdObjects(COMMAND_DEFAULT_CLASS): typetable = EvTable("|wtypeclass|n", "|wcount|n", "|w%%|n", border="table", align="l") typetable.align = 'l' dbtotals = ObjectDB.objects.object_totals() - for path, count in list(dbtotals.items()): + for path, count in dbtotals.items(): typetable.add_row(path, count, "%.2f" % ((float(count) / nobjs) * 100)) # last N table @@ -487,7 +487,7 @@ class CmdAccounts(COMMAND_DEFAULT_CLASS): # typeclass table dbtotals = AccountDB.objects.object_totals() typetable = EvTable("|wtypeclass|n", "|wcount|n", "|w%%|n", border="cells", align="l") - for path, count in list(dbtotals.items()): + for path, count in dbtotals.items(): typetable.add_row(path, count, "%.2f" % ((float(count) / naccounts) * 100)) # last N table plyrs = AccountDB.objects.all().order_by("db_date_created")[max(0, naccounts - nlim):] diff --git a/evennia/contrib/custom_gametime.py b/evennia/contrib/custom_gametime.py index 1446fcdf13..42a53d1386 100644 --- a/evennia/contrib/custom_gametime.py +++ b/evennia/contrib/custom_gametime.py @@ -105,7 +105,7 @@ def gametime_to_realtime(format=False, **kwargs): """ # Dynamically creates the list of units based on kwarg names and UNITs list rtime = 0 - for name, value in list(kwargs.items()): + for name, value in kwargs.items(): # Allow plural names (like mins instead of min) if name not in UNITS and name.endswith("s"): name = name[:-1] @@ -197,7 +197,7 @@ def real_seconds_until(**kwargs): # For each keyword, add in the unit's units.append(1) higher_unit = None - for unit, value in list(kwargs.items()): + for unit, value in kwargs.items(): # Get the unit's index if unit not in UNITS: raise ValueError("unknown unit".format(unit)) diff --git a/evennia/contrib/ingame_python/callbackhandler.py b/evennia/contrib/ingame_python/callbackhandler.py index bb0cddc597..625bfa182b 100644 --- a/evennia/contrib/ingame_python/callbackhandler.py +++ b/evennia/contrib/ingame_python/callbackhandler.py @@ -36,7 +36,7 @@ class CallbackHandler(object): handler = type(self).script if handler: dicts = handler.get_callbacks(self.obj) - for callback_name, in_list in list(dicts.items()): + for callback_name, in_list in dicts.items(): new_list = [] for callback in in_list: callback = self.format_callback(callback) diff --git a/evennia/contrib/ingame_python/scripts.py b/evennia/contrib/ingame_python/scripts.py index 097923878f..f79f08258a 100644 --- a/evennia/contrib/ingame_python/scripts.py +++ b/evennia/contrib/ingame_python/scripts.py @@ -129,7 +129,7 @@ class EventHandler(DefaultScript): while not classes.empty(): typeclass = classes.get() typeclass_name = typeclass.__module__ + "." + typeclass.__name__ - for key, etype in list(all_events.get(typeclass_name, {}).items()): + for key, etype in all_events.get(typeclass_name, {}).items(): if key in invalid: continue if etype[0] is None: # Invalidate @@ -186,7 +186,7 @@ class EventHandler(DefaultScript): """ obj_callbacks = self.db.callbacks.get(obj, {}) callbacks = {} - for callback_name, callback_list in list(obj_callbacks.items()): + for callback_name, callback_list in obj_callbacks.items(): new_list = [] for i, callback in enumerate(callback_list): callback = dict(callback) @@ -362,7 +362,7 @@ class EventHandler(DefaultScript): self.db.locked[i] = (t_obj, t_callback_name, t_number - 1) # Delete time-related callbacks associated with this object - for script in list(obj.scripts.all()): + for script in obj.scripts.all(): if isinstance(script, TimecallbackScript): if script.obj is obj and script.db.callback_name == callback_name: if script.db.number == number: @@ -576,7 +576,7 @@ class EventHandler(DefaultScript): # Collect and freeze current locals locals = {} - for key, value in list(self.ndb.current_locals.items()): + for key, value in self.ndb.current_locals.items(): try: dbserialize(value) except TypeError: diff --git a/evennia/contrib/ingame_python/utils.py b/evennia/contrib/ingame_python/utils.py index 47314f8a88..43d87ade13 100644 --- a/evennia/contrib/ingame_python/utils.py +++ b/evennia/contrib/ingame_python/utils.py @@ -65,7 +65,7 @@ def register_events(path_or_typeclass): # If the script is started, add the event directly. # Otherwise, add it to the temporary storage. - for name, tup in list(getattr(typeclass, "_events", {}).items()): + for name, tup in getattr(typeclass, "_events", {}).items(): if len(tup) == 4: variables, help_text, custom_call, custom_add = tup elif len(tup) == 3: diff --git a/evennia/contrib/tutorial_world/objects.py b/evennia/contrib/tutorial_world/objects.py index 779f37aba4..48ba6bac20 100644 --- a/evennia/contrib/tutorial_world/objects.py +++ b/evennia/contrib/tutorial_world/objects.py @@ -689,7 +689,7 @@ class CrumblingWall(TutorialObject, DefaultExit): "crisscross the wall, making it hard to clearly see its stony surface. Maybe you could " "try to |wshift|n or |wmove|n them.\n"] # display the root positions to help with the puzzle - for key, pos in list(self.db.root_pos.items()): + for key, pos in self.db.root_pos.items(): result.append("\n" + self._translate_position(key, pos)) self.db.desc = "".join(result) diff --git a/evennia/contrib/wilderness.py b/evennia/contrib/wilderness.py index 6d6ebf6649..83981c5700 100644 --- a/evennia/contrib/wilderness.py +++ b/evennia/contrib/wilderness.py @@ -249,7 +249,7 @@ class WildernessScript(DefaultScript): """ Called when the script is started and also after server reloads. """ - for coordinates, room in list(self.db.rooms.items()): + for coordinates, room in self.db.rooms.items(): room.ndb.wildernessscript = self room.ndb.active_coordinates = coordinates for item in list(self.db.itemcoordinates.keys()): diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index a354764236..8f51477817 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -959,7 +959,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): self.attributes.batch_add(*cdict["attributes"]) if cdict.get("nattributes"): # this should be a dict of nattrname:value - for key, value in list(cdict["nattributes"].items()): + for key, value in cdict["nattributes"].items(): self.nattributes.add(key, value) del self._createdict diff --git a/evennia/scripts/taskhandler.py b/evennia/scripts/taskhandler.py index f4f94e75d0..2b1dd25758 100644 --- a/evennia/scripts/taskhandler.py +++ b/evennia/scripts/taskhandler.py @@ -47,7 +47,7 @@ class TaskHandler(object): tasks = value # At this point, `tasks` contains a dictionary of still-serialized tasks - for task_id, value in list(tasks.items()): + for task_id, value in tasks.items(): date, callback, args, kwargs = dbunserialize(value) if isinstance(callback, tuple): # `callback` can be an object and name for instance methods @@ -64,7 +64,7 @@ class TaskHandler(object): def save(self): """Save the tasks in ServerConfig.""" - for task_id, (date, callback, args, kwargs) in list(self.tasks.items()): + for task_id, (date, callback, args, kwargs) in self.tasks.items(): if task_id in self.to_save: continue @@ -127,7 +127,7 @@ class TaskHandler(object): else: safe_args.append(arg) - for key, value in list(kwargs.items()): + for key, value in kwargs.items(): try: dbserialize(value) except (TypeError, AttributeError): @@ -187,7 +187,7 @@ class TaskHandler(object): """ now = datetime.now() - for task_id, (date, callbac, args, kwargs) in list(self.tasks.items()): + for task_id, (date, callbac, args, kwargs) in self.tasks.items(): seconds = max(0, (date - now).total_seconds()) task.deferLater(reactor, seconds, self.do_task, task_id) diff --git a/evennia/scripts/tickerhandler.py b/evennia/scripts/tickerhandler.py index 5ca5384b76..cdf29ae0c5 100644 --- a/evennia/scripts/tickerhandler.py +++ b/evennia/scripts/tickerhandler.py @@ -286,7 +286,7 @@ class TickerPool(object): if interval and interval in self.tickers: self.tickers[interval].stop() else: - for ticker in list(self.tickers.values()): + for ticker in self.tickers.values(): ticker.stop() @@ -395,7 +395,7 @@ class TickerHandler(object): store_key[2])} # a path given # update the timers for the tickers - for store_key, (args, kwargs) in list(to_save.items()): + for store_key, (args, kwargs) in to_save.items(): interval = store_key[1] # this is a mutable, so it's updated in-place in ticker_storage kwargs["_start_delay"] = start_delays.get(interval, None) diff --git a/evennia/server/evennia_launcher.py b/evennia/server/evennia_launcher.py index f96001fc06..8cbb87c9fd 100644 --- a/evennia/server/evennia_launcher.py +++ b/evennia/server/evennia_launcher.py @@ -995,7 +995,7 @@ def list_settings(keys): keys = [key.upper() for key in keys] confs = dict((key, var) for key, var in list(evsettings.__dict__.items()) if key in keys) - for key, val in list(confs.items()): + for key, val in confs.items(): table.add_row(key, str(val)) print(table) diff --git a/evennia/server/portal/mssp.py b/evennia/server/portal/mssp.py index 254d1b92fe..5442ccc320 100644 --- a/evennia/server/portal/mssp.py +++ b/evennia/server/portal/mssp.py @@ -191,7 +191,7 @@ class Mssp(object): self.mssp_table.update(MSSPTable_CUSTOM) varlist = '' - for variable, value in list(self.mssp_table.items()): + for variable, value in self.mssp_table.items(): if callable(value): value = value() if utils.is_iter(value): diff --git a/evennia/server/portal/portalsessionhandler.py b/evennia/server/portal/portalsessionhandler.py index c06757b798..ffc496998f 100644 --- a/evennia/server/portal/portalsessionhandler.py +++ b/evennia/server/portal/portalsessionhandler.py @@ -188,7 +188,7 @@ class PortalSessionHandler(SessionHandler): # we set a watchdog to stop self.disconnect from deleting # sessions while we are looping over them. sessionhandler._disconnect_all = True - for session in list(sessionhandler.values()): + for session in sessionhandler.values(): session.disconnect() del sessionhandler._disconnect_all @@ -336,7 +336,7 @@ class PortalSessionHandler(SessionHandler): send command. """ - for session in list(self.values()): + for session in self.values(): self.data_out(session, text=[[message], {}]) def data_in(self, session, **kwargs): diff --git a/evennia/server/session.py b/evennia/server/session.py index 496d15bfeb..229fa3a8f6 100644 --- a/evennia/server/session.py +++ b/evennia/server/session.py @@ -117,7 +117,7 @@ class Session(object): sessdata (dict): Session data dictionary. """ - for propname, value in list(sessdata.items()): + for propname, value in sessdata.items(): setattr(self, propname, value) def at_sync(self): diff --git a/evennia/server/sessionhandler.py b/evennia/server/sessionhandler.py index 34030ade3c..45e1d30790 100644 --- a/evennia/server/sessionhandler.py +++ b/evennia/server/sessionhandler.py @@ -195,7 +195,7 @@ class SessionHandler(dict): "Helper function to convert data to AMP-safe (picketable) values" if isinstance(data, dict): newdict = {} - for key, part in list(data.items()): + for key, part in data.items(): newdict[key] = _validate(part) return newdict elif is_iter(data): @@ -349,7 +349,7 @@ class ServerSessionHandler(SessionHandler): # lingering references. del sess - for sessid, sessdict in list(portalsessionsdata.items()): + for sessid, sessdict in portalsessionsdata.items(): sess = _ServerSession() sess.sessionhandler = self sess.load_sync_data(sessdict) @@ -688,7 +688,7 @@ class ServerSessionHandler(SessionHandler): message (str): Message to send. """ - for session in list(self.values()): + for session in self.values(): self.data_out(session, text=message) def data_out(self, session, **kwargs): diff --git a/evennia/utils/batchprocessors.py b/evennia/utils/batchprocessors.py index 174a296943..a35e6d37f8 100644 --- a/evennia/utils/batchprocessors.py +++ b/evennia/utils/batchprocessors.py @@ -382,7 +382,7 @@ class BatchCodeProcessor(object): """ # define the execution environment environdict = {"settings_module": settings, "DEBUG": debug} - for key, value in list(extra_environ.items()): + for key, value in extra_environ.items(): environdict[key] = value # initializing the django settings at the top of code diff --git a/evennia/utils/eveditor.py b/evennia/utils/eveditor.py index 990c826b33..62a9d97ed1 100644 --- a/evennia/utils/eveditor.py +++ b/evennia/utils/eveditor.py @@ -965,7 +965,7 @@ class EvEditor(object): # If the line begins by one of the given keywords indent = self._indent - if any(line.startswith(kw) for kw in list(keywords.keys())): + if any(line.startswith(kw) for kw in keywords.keys()): # Get the keyword and matching begin tags keyword = [kw for kw in keywords if line.startswith(kw)][0] begin_tags = keywords[keyword] diff --git a/evennia/utils/evform.py b/evennia/utils/evform.py index 488a0c46a4..8b1f4afd5c 100644 --- a/evennia/utils/evform.py +++ b/evennia/utils/evform.py @@ -259,7 +259,7 @@ class EvForm(object): break # get rectangles and assign EvCells - for key, (iy, leftix, rightix) in list(cell_coords.items()): + for key, (iy, leftix, rightix) in cell_coords.items(): # scan up to find top of rectangle dy_up = 0 if iy > 0: @@ -294,7 +294,7 @@ class EvForm(object): mapping[key] = (iyup, leftix, width, height, EvCell(data, width=width, height=height, **options)) # get rectangles and assign Tables - for key, (iy, leftix, rightix) in list(table_coords.items()): + for key, (iy, leftix, rightix) in table_coords.items(): # scan up to find top of rectangle dy_up = 0 @@ -340,7 +340,7 @@ class EvForm(object): """ form = copy.copy(raw_form) - for key, (iy0, ix0, width, height, cell_or_table) in list(mapping.items()): + for key, (iy0, ix0, width, height, cell_or_table) in mapping.items(): # rect is a list of lines, each wide rect = cell_or_table.get() for il, rectline in enumerate(rect): diff --git a/evennia/utils/evtable.py b/evennia/utils/evtable.py index fadf64fd4a..10881e4bb0 100644 --- a/evennia/utils/evtable.py +++ b/evennia/utils/evtable.py @@ -816,7 +816,7 @@ class EvCell(object): self.trim_vertical = kwargs.pop("trim_vertical", self.trim_vertical) # fill all other properties - for key, value in list(kwargs.items()): + for key, value in kwargs.items(): setattr(self, key, value) # Handle sizes @@ -1525,7 +1525,7 @@ class EvTable(object): """ self.width = kwargs.pop("width", self.width) self.height = kwargs.pop("height", self.height) - for key, value in list(kwargs.items()): + for key, value in kwargs.items(): setattr(self, key, value) hchar = kwargs.pop("header_line_char", self.header_line_char) diff --git a/evennia/utils/spawner.py b/evennia/utils/spawner.py index 1afff3413d..b99f166018 100644 --- a/evennia/utils/spawner.py +++ b/evennia/utils/spawner.py @@ -238,7 +238,7 @@ def spawn(*prototypes, **kwargs): list(all_from_module(prototype_module).items()) if isinstance(val, dict))) # overload module's protparents with specifically given protparents protparents.update(kwargs.get("prototype_parents", {})) - for key, prototype in list(protparents.items()): + for key, prototype in protparents.items(): _validate_prototype(key, prototype, protparents, []) if "return_prototypes" in kwargs: From 7d524ac3286549369ec78e6574c0a4ab9b0c43c6 Mon Sep 17 00:00:00 2001 From: Ryan Stein Date: Thu, 2 Nov 2017 22:52:16 -0400 Subject: [PATCH 027/493] Unwrap `for` expressions in comprehensions, too. --- evennia/commands/default/building.py | 2 +- evennia/commands/default/system.py | 2 +- evennia/contrib/extended_room.py | 2 +- evennia/contrib/ingame_python/commands.py | 2 +- evennia/contrib/ingame_python/scripts.py | 2 +- evennia/contrib/ingame_python/utils.py | 2 +- evennia/contrib/rplanguage.py | 2 +- evennia/contrib/rpsystem.py | 8 ++++---- evennia/locks/lockhandler.py | 2 +- evennia/objects/objects.py | 2 +- evennia/scripts/tickerhandler.py | 4 ++-- evennia/server/evennia_launcher.py | 2 +- evennia/server/portal/portalsessionhandler.py | 2 +- evennia/server/server.py | 2 +- evennia/server/serversession.py | 2 +- evennia/server/session.py | 2 +- evennia/server/sessionhandler.py | 16 ++++++++-------- evennia/typeclasses/attributes.py | 10 +++++----- evennia/typeclasses/tags.py | 2 +- evennia/utils/dbserialize.py | 18 +++++++++--------- evennia/utils/evform.py | 10 +++++----- evennia/utils/evmenu.py | 2 +- evennia/utils/idmapper/models.py | 2 +- evennia/utils/spawner.py | 4 ++-- evennia/utils/utils.py | 4 ++-- 25 files changed, 54 insertions(+), 54 deletions(-) diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index 581a7f7561..8e732fd1ef 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -2058,7 +2058,7 @@ class CmdExamine(ObjManipCommand): except (TypeError, AttributeError): # an error means we are merging an object without a session pass - all_cmdsets = [cmdset for cmdset in list(dict(all_cmdsets).values())] + all_cmdsets = [cmdset for cmdset in dict(all_cmdsets).values()] all_cmdsets.sort(key=lambda x: x.priority, reverse=True) string += "\n|wMerged Cmdset(s)|n:\n %s" % ("\n ".join("%s [%s] (%s, prio %s)" % ( cmdset.path, cmdset.key, cmdset.mergetype, cmdset.priority) for cmdset in all_cmdsets)) diff --git a/evennia/commands/default/system.py b/evennia/commands/default/system.py index 1d80f3578f..3d091abccd 100644 --- a/evennia/commands/default/system.py +++ b/evennia/commands/default/system.py @@ -799,7 +799,7 @@ class CmdServerLoad(COMMAND_DEFAULT_CLASS): # object cache count (note that sys.getsiseof is not called so this works for pypy too. total_num, cachedict = _IDMAPPER.cache_size() - sorted_cache = sorted([(key, num) for key, num in list(cachedict.items()) if num > 0], + sorted_cache = sorted([(key, num) for key, num in cachedict.items() if num > 0], key=lambda tup: tup[1], reverse=True) memtable = EvTable("entity name", "number", "idmapper %", align="l") for tup in sorted_cache: diff --git a/evennia/contrib/extended_room.py b/evennia/contrib/extended_room.py index 21ceb8bba9..7a29593573 100644 --- a/evennia/contrib/extended_room.py +++ b/evennia/contrib/extended_room.py @@ -398,7 +398,7 @@ class CmdExtendedDesc(default_cmds.CmdDesc): # No args given. Return all details on location string = "|wDetails on %s|n:" % location details = "\n".join(" |w%s|n: %s" - % (key, utils.crop(text)) for key, text in list(location.db.details.items())) + % (key, utils.crop(text)) for key, text in location.db.details.items()) caller.msg("%s\n%s" % (string, details) if details else "%s None." % string) return if not self.rhs: diff --git a/evennia/contrib/ingame_python/commands.py b/evennia/contrib/ingame_python/commands.py index a8d14ba476..b1e01473b6 100644 --- a/evennia/contrib/ingame_python/commands.py +++ b/evennia/contrib/ingame_python/commands.py @@ -503,7 +503,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS): obj = self.obj callback_name = self.callback_name handler = self.handler - tasks = [(k, v[0], v[1], v[2]) for k, v in list(handler.db.tasks.items())] + tasks = [(k, v[0], v[1], v[2]) for k, v in handler.db.tasks.items()] if obj: tasks = [task for task in tasks if task[2] is obj] if callback_name: diff --git a/evennia/contrib/ingame_python/scripts.py b/evennia/contrib/ingame_python/scripts.py index f79f08258a..d0706db2e6 100644 --- a/evennia/contrib/ingame_python/scripts.py +++ b/evennia/contrib/ingame_python/scripts.py @@ -436,7 +436,7 @@ class EventHandler(DefaultScript): type(obj), variable, i)) return False else: - locals = {key: value for key, value in list(locals.items())} + locals = {key: value for key, value in locals.items()} callbacks = self.get_callbacks(obj).get(callback_name, []) if event: diff --git a/evennia/contrib/ingame_python/utils.py b/evennia/contrib/ingame_python/utils.py index 43d87ade13..fde97baaad 100644 --- a/evennia/contrib/ingame_python/utils.py +++ b/evennia/contrib/ingame_python/utils.py @@ -116,7 +116,7 @@ def get_next_wait(format): units = ["min", "hour", "day", "month", "year"] elif calendar == "custom": rsu = custom_rsu - back = dict([(value, name) for name, value in list(UNITS.items())]) + back = dict([(value, name) for name, value in UNITS.items()]) sorted_units = sorted(back.items()) del sorted_units[0] units = [n for v, n in sorted_units] diff --git a/evennia/contrib/rplanguage.py b/evennia/contrib/rplanguage.py index 4784bb1c08..a39641a563 100644 --- a/evennia/contrib/rplanguage.py +++ b/evennia/contrib/rplanguage.py @@ -254,7 +254,7 @@ class LanguageHandler(DefaultScript): if manual_translations: # update with manual translations - translation.update(dict((key.lower(), value.lower()) for key, value in list(manual_translations.items()))) + translation.update(dict((key.lower(), value.lower()) for key, value in manual_translations.items())) # store data storage = {"translation": translation, diff --git a/evennia/contrib/rpsystem.py b/evennia/contrib/rpsystem.py index 574e6b07ed..52b92ee896 100644 --- a/evennia/contrib/rpsystem.py +++ b/evennia/contrib/rpsystem.py @@ -531,11 +531,11 @@ def send_emote(sender, receivers, emote, anonymous_add="first"): try: recog_get = receiver.recog.get - receiver_sdesc_mapping = dict((ref, process_recog(recog_get(obj), obj)) for ref, obj in list(obj_mapping.items())) + receiver_sdesc_mapping = dict((ref, process_recog(recog_get(obj), obj)) for ref, obj in obj_mapping.items()) except AttributeError: receiver_sdesc_mapping = dict((ref, process_sdesc(obj.sdesc.get(), obj) if hasattr(obj, "sdesc") else process_sdesc(obj.key, obj)) - for ref, obj in list(obj_mapping.items())) + for ref, obj in obj_mapping.items()) # make sure receiver always sees their real name rkey = "#%i" % receiver.id if rkey in receiver_sdesc_mapping: @@ -684,9 +684,9 @@ class RecogHandler(object): obj2regex = self.obj.attributes.get("_recog_obj2regex", default={}) obj2recog = self.obj.attributes.get("_recog_obj2recog", default={}) self.obj2regex = dict((obj, re.compile(regex, _RE_FLAGS)) - for obj, regex in list(obj2regex.items()) if obj) + for obj, regex in obj2regex.items() if obj) self.obj2recog = dict((obj, recog) - for obj, recog in list(obj2recog.items()) if obj) + for obj, recog in obj2recog.items() if obj) def add(self, obj, recog, max_length=60): """ diff --git a/evennia/locks/lockhandler.py b/evennia/locks/lockhandler.py index 3d1787ad9d..38113e8613 100644 --- a/evennia/locks/lockhandler.py +++ b/evennia/locks/lockhandler.py @@ -269,7 +269,7 @@ class LockHandler(object): """ Store locks to obj """ - self.obj.lock_storage = ";".join([tup[2] for tup in list(self.locks.values())]) + self.obj.lock_storage = ";".join([tup[2] for tup in self.locks.values()]) def cache_lock_bypass(self, obj): """ diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index 8f51477817..d7095012e7 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -615,7 +615,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): if mapping: substitutions = {t: sub.get_display_name(obj) if hasattr(sub, 'get_display_name') - else str(sub) for t, sub in list(mapping.items())} + else str(sub) for t, sub in mapping.items()} outmessage = inmessage.format(**substitutions) else: outmessage = inmessage diff --git a/evennia/scripts/tickerhandler.py b/evennia/scripts/tickerhandler.py index cdf29ae0c5..b840bf2d9c 100644 --- a/evennia/scripts/tickerhandler.py +++ b/evennia/scripts/tickerhandler.py @@ -386,10 +386,10 @@ class TickerHandler(object): if self.ticker_storage: # get the current times so the tickers can be restarted with a delay later start_delays = dict((interval, ticker.task.next_call_time()) - for interval, ticker in list(self.ticker_pool.tickers.items())) + for interval, ticker in self.ticker_pool.tickers.items()) # remove any subscriptions that lost its object in the interim - to_save = {store_key: (args, kwargs) for store_key, (args, kwargs) in list(self.ticker_storage.items()) + to_save = {store_key: (args, kwargs) for store_key, (args, kwargs) in self.ticker_storage.items() if ((store_key[1] and ("_obj" in kwargs and kwargs["_obj"].pk) and hasattr(kwargs["_obj"], store_key[1])) or # a valid method with existing obj store_key[2])} # a path given diff --git a/evennia/server/evennia_launcher.py b/evennia/server/evennia_launcher.py index 8cbb87c9fd..789e0be939 100644 --- a/evennia/server/evennia_launcher.py +++ b/evennia/server/evennia_launcher.py @@ -993,7 +993,7 @@ def list_settings(keys): # a specific key table = evtable.EvTable(width=131) keys = [key.upper() for key in keys] - confs = dict((key, var) for key, var in list(evsettings.__dict__.items()) + confs = dict((key, var) for key, var in evsettings.__dict__.items() if key in keys) for key, val in confs.items(): table.add_row(key, str(val)) diff --git a/evennia/server/portal/portalsessionhandler.py b/evennia/server/portal/portalsessionhandler.py index ffc496998f..31cb54f00d 100644 --- a/evennia/server/portal/portalsessionhandler.py +++ b/evennia/server/portal/portalsessionhandler.py @@ -141,7 +141,7 @@ class PortalSessionHandler(SessionHandler): if self.portal.amp_protocol: # we only send sessdata that should not have changed # at the server level at this point - sessdata = dict((key, val) for key, val in list(sessdata.items()) if key in ("protocol_key", + sessdata = dict((key, val) for key, val in sessdata.items() if key in ("protocol_key", "address", "sessid", "csessid", diff --git a/evennia/server/server.py b/evennia/server/server.py index 457302b5f2..c84884acaa 100644 --- a/evennia/server/server.py +++ b/evennia/server/server.py @@ -131,7 +131,7 @@ def _server_maintenance(): # handle idle timeouts if _IDLE_TIMEOUT > 0: reason = _("idle timeout exceeded") - for session in (sess for sess in list(SESSIONS.values()) + for session in (sess for sess in SESSIONS.values() if (now - sess.cmd_last) > _IDLE_TIMEOUT): if not session.account or not \ session.account.access(session.account, "noidletimeout", default=False): diff --git a/evennia/server/serversession.py b/evennia/server/serversession.py index c86e189b64..6e7d31c0b6 100644 --- a/evennia/server/serversession.py +++ b/evennia/server/serversession.py @@ -141,7 +141,7 @@ class NAttributeHandler(object): """ if return_tuples: - return [(key, value) for (key, value) in list(self._store.items()) if not key.startswith("_")] + return [(key, value) for (key, value) in self._store.items() if not key.startswith("_")] return [key for key in self._store if not key.startswith("_")] diff --git a/evennia/server/session.py b/evennia/server/session.py index 229fa3a8f6..70be0708d7 100644 --- a/evennia/server/session.py +++ b/evennia/server/session.py @@ -105,7 +105,7 @@ class Session(object): the keys given by self._attrs_to_sync. """ - return dict((key, value) for key, value in list(self.__dict__.items()) + return dict((key, value) for key, value in self.__dict__.items() if key in self._attrs_to_sync) def load_sync_data(self, sessdata): diff --git a/evennia/server/sessionhandler.py b/evennia/server/sessionhandler.py index 45e1d30790..4caf0c8b1e 100644 --- a/evennia/server/sessionhandler.py +++ b/evennia/server/sessionhandler.py @@ -136,7 +136,7 @@ class SessionHandler(dict): if include_unloggedin: return listvalues(self) else: - return [session for session in list(self.values()) if session.logged_in] + return [session for session in self.values() if session.logged_in] def get_all_sync_data(self): """ @@ -147,7 +147,7 @@ class SessionHandler(dict): syncdata (dict): A dict of sync data. """ - return dict((sessid, sess.get_sync_data()) for sessid, sess in list(self.items())) + return dict((sessid, sess.get_sync_data()) for sessid, sess in self.items()) def clean_senddata(self, session, kwargs): """ @@ -562,7 +562,7 @@ class ServerSessionHandler(SessionHandler): """ uid = curr_session.uid - doublet_sessions = [sess for sess in list(self.values()) + doublet_sessions = [sess for sess in self.values() if sess.logged_in and sess.uid == uid and sess != curr_session] @@ -577,7 +577,7 @@ class ServerSessionHandler(SessionHandler): """ tcurr = time.time() reason = _("Idle timeout exceeded, disconnecting.") - for session in (session for session in list(self.values()) + for session in (session for session in self.values() if session.logged_in and _IDLE_TIMEOUT > 0 and (tcurr - session.cmd_last) > _IDLE_TIMEOUT): self.disconnect(session, reason=reason) @@ -592,7 +592,7 @@ class ServerSessionHandler(SessionHandler): naccount (int): Number of connected accounts """ - return len(set(session.uid for session in list(self.values()) if session.logged_in)) + return len(set(session.uid for session in self.values() if session.logged_in)) def all_connected_accounts(self): """ @@ -603,7 +603,7 @@ class ServerSessionHandler(SessionHandler): amount of Sessions due to multi-playing). """ - return list(set(session.account for session in list(self.values()) if session.logged_in and session.account)) + return list(set(session.account for session in self.values() if session.logged_in and session.account)) def session_from_sessid(self, sessid): """ @@ -650,7 +650,7 @@ class ServerSessionHandler(SessionHandler): """ uid = account.uid - return [session for session in list(self.values()) if session.logged_in and session.uid == uid] + return [session for session in self.values() if session.logged_in and session.uid == uid] def sessions_from_puppet(self, puppet): """ @@ -677,7 +677,7 @@ class ServerSessionHandler(SessionHandler): csessid (str): The session hash """ - return [session for session in list(self.values()) + return [session for session in self.values() if session.csessid and session.csessid == csessid] def announce_all(self, message): diff --git a/evennia/typeclasses/attributes.py b/evennia/typeclasses/attributes.py index 5bae73573d..bfd30384cf 100644 --- a/evennia/typeclasses/attributes.py +++ b/evennia/typeclasses/attributes.py @@ -299,7 +299,7 @@ class AttributeHandler(object): # for this category before catkey = "-%s" % category if _TYPECLASS_AGGRESSIVE_CACHE and catkey in self._catcache: - return [attr for key, attr in list(self._cache.items()) if key.endswith(catkey) and attr] + return [attr for key, attr in self._cache.items() if key.endswith(catkey) and attr] else: # we have to query to make this category up-date in the cache query = {"%s__id" % self._model: self._objid, @@ -655,10 +655,10 @@ class AttributeHandler(object): if not self._cache_complete: self._fullcache() if accessing_obj: - [attr.delete() for attr in list(self._cache.values()) + [attr.delete() for attr in self._cache.values() if attr and attr.access(accessing_obj, self._attredit, default=default_access)] else: - [attr.delete() for attr in list(self._cache.values()) if attr and attr.pk] + [attr.delete() for attr in self._cache.values() if attr and attr.pk] self._cache = {} self._catcache = {} self._cache_complete = False @@ -682,7 +682,7 @@ class AttributeHandler(object): """ if not self._cache_complete: self._fullcache() - attrs = sorted([attr for attr in list(self._cache.values()) if attr], + attrs = sorted([attr for attr in self._cache.values() if attr], key=lambda o: o.id) if accessing_obj: return [attr for attr in attrs @@ -1003,5 +1003,5 @@ class NAttributeHandler(object): """ if return_tuples: - return [(key, value) for (key, value) in list(self._store.items()) if not key.startswith("_")] + return [(key, value) for (key, value) in self._store.items() if not key.startswith("_")] return [key for key in self._store if not key.startswith("_")] diff --git a/evennia/typeclasses/tags.py b/evennia/typeclasses/tags.py index 1f1e9b54ea..dbcd379213 100644 --- a/evennia/typeclasses/tags.py +++ b/evennia/typeclasses/tags.py @@ -169,7 +169,7 @@ class TagHandler(object): # for this category before catkey = "-%s" % category if _TYPECLASS_AGGRESSIVE_CACHE and catkey in self._catcache: - return [tag for key, tag in list(self._cache.items()) if key.endswith(catkey)] + return [tag for key, tag in self._cache.items() if key.endswith(catkey)] else: # we have to query to make this category up-date in the cache query = {"%s__id" % self._model: self._objid, diff --git a/evennia/utils/dbserialize.py b/evennia/utils/dbserialize.py index c4bc141a4d..b59c5e96ef 100644 --- a/evennia/utils/dbserialize.py +++ b/evennia/utils/dbserialize.py @@ -191,7 +191,7 @@ class _SaverMutable(object): return dat elif dtype == dict: dat = _SaverDict(_parent=parent) - dat._data.update((key, process_tree(val, dat)) for key, val in list(item.items())) + dat._data.update((key, process_tree(val, dat)) for key, val in item.items()) return dat elif dtype == set: dat = _SaverSet(_parent=parent) @@ -500,11 +500,11 @@ def to_pickle(data): elif dtype in (list, _SaverList): return [process_item(val) for val in item] elif dtype in (dict, _SaverDict): - return dict((process_item(key), process_item(val)) for key, val in list(item.items())) + return dict((process_item(key), process_item(val)) for key, val in item.items()) elif dtype in (set, _SaverSet): return set(process_item(val) for val in item) elif dtype in (OrderedDict, _SaverOrderedDict): - return OrderedDict((process_item(key), process_item(val)) for key, val in list(item.items())) + return OrderedDict((process_item(key), process_item(val)) for key, val in item.items()) elif dtype in (deque, _SaverDeque): return deque(process_item(val) for val in item) @@ -555,11 +555,11 @@ def from_pickle(data, db_obj=None): elif dtype == tuple: return tuple(process_item(val) for val in item) elif dtype == dict: - return dict((process_item(key), process_item(val)) for key, val in list(item.items())) + return dict((process_item(key), process_item(val)) for key, val in item.items()) elif dtype == set: return set(process_item(val) for val in item) elif dtype == OrderedDict: - return OrderedDict((process_item(key), process_item(val)) for key, val in list(item.items())) + return OrderedDict((process_item(key), process_item(val)) for key, val in item.items()) elif dtype == deque: return deque(process_item(val) for val in item) elif hasattr(item, '__iter__'): @@ -588,7 +588,7 @@ def from_pickle(data, db_obj=None): elif dtype == dict: dat = _SaverDict(_parent=parent) dat._data.update((process_item(key), process_tree(val, dat)) - for key, val in list(item.items())) + for key, val in item.items()) return dat elif dtype == set: dat = _SaverSet(_parent=parent) @@ -597,7 +597,7 @@ def from_pickle(data, db_obj=None): elif dtype == OrderedDict: dat = _SaverOrderedDict(_parent=parent) dat._data.update((process_item(key), process_tree(val, dat)) - for key, val in list(item.items())) + for key, val in item.items()) return dat elif dtype == deque: dat = _SaverDeque(_parent=parent) @@ -625,7 +625,7 @@ def from_pickle(data, db_obj=None): elif dtype == dict: dat = _SaverDict(_db_obj=db_obj) dat._data.update((process_item(key), process_tree(val, dat)) - for key, val in list(data.items())) + for key, val in data.items()) return dat elif dtype == set: dat = _SaverSet(_db_obj=db_obj) @@ -634,7 +634,7 @@ def from_pickle(data, db_obj=None): elif dtype == OrderedDict: dat = _SaverOrderedDict(_db_obj=db_obj) dat._data.update((process_item(key), process_tree(val, dat)) - for key, val in list(data.items())) + for key, val in data.items()) return dat elif dtype == deque: dat = _SaverDeque(_db_obj=db_obj) diff --git a/evennia/utils/evform.py b/evennia/utils/evform.py index 8b1f4afd5c..dfa5cb3729 100644 --- a/evennia/utils/evform.py +++ b/evennia/utils/evform.py @@ -160,7 +160,7 @@ def _to_ansi(obj, regexable=False): # escape the |-structure twice. obj = _ANSI_ESCAPE.sub(r"||||", obj) if isinstance(obj, dict): - return dict((key, _to_ansi(value, regexable=regexable)) for key, value in list(obj.items())) + return dict((key, _to_ansi(value, regexable=regexable)) for key, value in obj.items()) elif is_iter(obj): return [_to_ansi(o) for o in obj] else: @@ -196,8 +196,8 @@ class EvForm(object): self.filename = filename self.input_form_dict = form - self.cells_mapping = dict((to_str(key, force_string=True), value) for key, value in list(cells.items())) if cells else {} - self.tables_mapping = dict((to_str(key, force_string=True), value) for key, value in list(tables.items())) if tables else {} + self.cells_mapping = dict((to_str(key, force_string=True), value) for key, value in cells.items()) if cells else {} + self.tables_mapping = dict((to_str(key, force_string=True), value) for key, value in tables.items()) if tables else {} self.cellchar = "x" self.tablechar = "c" @@ -367,8 +367,8 @@ class EvForm(object): kwargs.pop("width", None) kwargs.pop("height", None) - new_cells = dict((to_str(key, force_string=True), value) for key, value in list(cells.items())) if cells else {} - new_tables = dict((to_str(key, force_string=True), value) for key, value in list(tables.items())) if tables else {} + new_cells = dict((to_str(key, force_string=True), value) for key, value in cells.items()) if cells else {} + new_tables = dict((to_str(key, force_string=True), value) for key, value in tables.items()) if tables else {} self.cells_mapping.update(new_cells) self.tables_mapping.update(new_tables) diff --git a/evennia/utils/evmenu.py b/evennia/utils/evmenu.py index c5bdd730a6..2264af4d81 100644 --- a/evennia/utils/evmenu.py +++ b/evennia/utils/evmenu.py @@ -511,7 +511,7 @@ class EvMenu(object): else: # a python path of a module module = mod_import(menudata) - return dict((key, func) for key, func in list(module.__dict__.items()) + return dict((key, func) for key, func in module.__dict__.items() if isfunction(func) and not key.startswith("_")) def _format_node(self, nodetext, optionlist): diff --git a/evennia/utils/idmapper/models.py b/evennia/utils/idmapper/models.py index 8da63663db..e224143a10 100644 --- a/evennia/utils/idmapper/models.py +++ b/evennia/utils/idmapper/models.py @@ -328,7 +328,7 @@ class SharedMemoryModel(with_metaclass(SharedMemoryModelBase, Model)): if force: cls.__dbclass__.__instance_cache__ = {} else: - cls.__dbclass__.__instance_cache__ = dict((key, obj) for key, obj in list(cls.__dbclass__.__instance_cache__.items()) + cls.__dbclass__.__instance_cache__ = dict((key, obj) for key, obj in cls.__dbclass__.__instance_cache__.items() if not obj.at_idmapper_flush()) #flush_instance_cache = classmethod(flush_instance_cache) diff --git a/evennia/utils/spawner.py b/evennia/utils/spawner.py index b99f166018..7f73dfaa5c 100644 --- a/evennia/utils/spawner.py +++ b/evennia/utils/spawner.py @@ -288,11 +288,11 @@ def spawn(*prototypes, **kwargs): # extract ndb assignments nattributes = dict((key.split("_", 1)[1], value() if callable(value) else value) - for key, value in list(prot.items()) if key.startswith("ndb_")) + for key, value in prot.items() if key.startswith("ndb_")) # the rest are attributes simple_attributes = [(key, value()) if callable(value) else (key, value) - for key, value in list(prot.items()) if not key.startswith("ndb_")] + for key, value in prot.items() if not key.startswith("ndb_")] attributes = attributes + simple_attributes attributes = [tup for tup in attributes if not tup[0] in _CREATE_OBJECT_KWARGS] diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index 9562253852..f083c19bcf 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -1242,7 +1242,7 @@ def variable_from_module(module, variable=None, default=None): result.append(mod.__dict__.get(var, default)) else: # get all - result = [val for key, val in list(mod.__dict__.items()) + result = [val for key, val in mod.__dict__.items() if not (key.startswith("_") or ismodule(val))] if len(result) == 1: @@ -1612,7 +1612,7 @@ def deepsize(obj, max_depth=4): _recurse(ref, dct, depth + 1) sizedict = {} _recurse(obj, sizedict, 0) - size = getsizeof(obj) + sum([p[1] for p in list(sizedict.values())]) + size = getsizeof(obj) + sum([p[1] for p in sizedict.values()]) return size From 6f91e1e546735dfca1fe2f94682acfbc035cb4b2 Mon Sep 17 00:00:00 2001 From: Ryan Stein Date: Fri, 3 Nov 2017 12:36:45 -0400 Subject: [PATCH 028/493] Remove to_unicode. --- evennia/accounts/accounts.py | 3 +- evennia/commands/cmdhandler.py | 4 +-- evennia/commands/default/account.py | 2 +- evennia/commands/default/building.py | 1 - evennia/commands/default/unloggedin.py | 2 +- evennia/contrib/mapbuilder.py | 2 +- evennia/objects/manager.py | 6 ++-- evennia/objects/objects.py | 3 +- evennia/server/inputfuncs.py | 4 +-- evennia/server/sessionhandler.py | 2 +- evennia/typeclasses/managers.py | 4 +-- evennia/utils/ansi.py | 4 +-- evennia/utils/evform.py | 6 ++-- evennia/utils/evtable.py | 4 +-- evennia/utils/utils.py | 46 ++------------------------ 15 files changed, 23 insertions(+), 70 deletions(-) diff --git a/evennia/accounts/accounts.py b/evennia/accounts/accounts.py index 277cd64575..204ff93bf5 100644 --- a/evennia/accounts/accounts.py +++ b/evennia/accounts/accounts.py @@ -22,7 +22,7 @@ from evennia.comms.models import ChannelDB from evennia.commands import cmdhandler from evennia.utils import logger from evennia.utils.utils import (lazy_property, - make_iter, to_unicode, is_iter, + make_iter, is_iter, variable_from_module) from evennia.typeclasses.attributes import NickHandler from evennia.scripts.scripthandler import ScriptHandler @@ -446,7 +446,6 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)): commands at run-time. """ - raw_string = to_unicode(raw_string) raw_string = self.nicks.nickreplace(raw_string, categories=("inputline", "channel"), include_account=False) if not session and _MULTISESSION_MODE in (0, 1): # for these modes we use the first/only session diff --git a/evennia/commands/cmdhandler.py b/evennia/commands/cmdhandler.py index 91ad60cc54..bc98dd5748 100644 --- a/evennia/commands/cmdhandler.py +++ b/evennia/commands/cmdhandler.py @@ -46,7 +46,7 @@ from django.conf import settings from evennia.commands.command import InterruptCommand from evennia.comms.channelhandler import CHANNELHANDLER from evennia.utils import logger, utils -from evennia.utils.utils import string_suggestions, to_unicode +from evennia.utils.utils import string_suggestions from django.utils.translation import ugettext as _ @@ -618,8 +618,6 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess finally: _COMMAND_NESTING[called_by] -= 1 - raw_string = to_unicode(raw_string, force_string=True) - session, account, obj = session, None, None if callertype == "session": session = called_by diff --git a/evennia/commands/default/account.py b/evennia/commands/default/account.py index d09ab08b65..85a25a5cac 100644 --- a/evennia/commands/default/account.py +++ b/evennia/commands/default/account.py @@ -533,7 +533,7 @@ class CmdOption(COMMAND_DEFAULT_CLASS): def validate_encoding(new_encoding): # helper: change encoding try: - utils.to_str(utils.to_unicode("test-string"), encoding=new_encoding) + b"test-string".decode(new_encoding) except LookupError: raise RuntimeError("The encoding '|w%s|n' is invalid. " % new_encoding) return val diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index 8e732fd1ef..a41bf872c1 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -1940,7 +1940,6 @@ class CmdExamine(ObjManipCommand): if not isinstance(value, str): value = utils.to_str(value, force_string=True) value = utils.crop(value) - value = utils.to_unicode(value) string = "\n %s = %s" % (attr, value) string = raw(string) diff --git a/evennia/commands/default/unloggedin.py b/evennia/commands/default/unloggedin.py index 67e15dfd38..149fd03824 100644 --- a/evennia/commands/default/unloggedin.py +++ b/evennia/commands/default/unloggedin.py @@ -481,7 +481,7 @@ class CmdUnconnectedEncoding(COMMAND_DEFAULT_CLASS): old_encoding = self.session.protocol_flags.get("ENCODING", None) encoding = self.args try: - utils.to_str(utils.to_unicode("test-string"), encoding=encoding) + utils.to_str(b"test-string".decode(encoding)) except LookupError: string = "|rThe encoding '|w%s|r' is invalid. Keeping the previous encoding '|w%s|r'.|n"\ % (encoding, old_encoding) diff --git a/evennia/contrib/mapbuilder.py b/evennia/contrib/mapbuilder.py index 7d9b07d136..b4cc70778e 100644 --- a/evennia/contrib/mapbuilder.py +++ b/evennia/contrib/mapbuilder.py @@ -324,7 +324,7 @@ def build_map(caller, game_map, legend, iterations=1, build_exits=True): for x in range(len(game_map[y])): for key in legend: # obs - we must use == for unicode - if utils.to_unicode(game_map[y][x]) == utils.to_unicode(key): + if game_map[y][x] == key: room = legend[key](x, y, iteration=iteration, room_dict=room_dict, caller=caller) diff --git a/evennia/objects/manager.py b/evennia/objects/manager.py index 22ef174636..a7c41b66bf 100644 --- a/evennia/objects/manager.py +++ b/evennia/objects/manager.py @@ -7,7 +7,7 @@ from django.db.models import Q from django.conf import settings from django.db.models.fields import exceptions from evennia.typeclasses.managers import TypedObjectManager, TypeclassManager -from evennia.utils.utils import to_unicode, is_iter, make_iter, string_partial_matching +from evennia.utils.utils import is_iter, make_iter, string_partial_matching from builtins import int __all__ = ("ObjectManager",) @@ -72,7 +72,7 @@ class ObjectDBManager(TypedObjectManager): match (Object or list): One or more matching results. """ - ostring = to_unicode(ostring).lstrip('*') + ostring = str(ostring).lstrip('*') # simplest case - search by dbref dbref = self.dbref(ostring) if dbref: @@ -196,8 +196,6 @@ class ObjectDBManager(TypedObjectManager): typeclasses (list, optional): List of typeclass-path strings to restrict matches with """ - if isinstance(property_value, str): - property_value = to_unicode(property_value) if isinstance(property_name, str): if not property_name.startswith('db_'): property_name = "db_%s" % property_name diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index d7095012e7..0fc7f2f5aa 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -22,7 +22,7 @@ from evennia.commands import cmdhandler from evennia.utils import search from evennia.utils import logger from evennia.utils.utils import (variable_from_module, lazy_property, - make_iter, to_unicode, is_iter) + make_iter, is_iter) from django.utils.translation import ugettext as _ _MULTISESSION_MODE = settings.MULTISESSION_MODE @@ -479,7 +479,6 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): """ # nick replacement - we require full-word matching. # do text encoding conversion - raw_string = to_unicode(raw_string) raw_string = self.nicks.nickreplace(raw_string, categories=("inputline", "channel"), include_account=True) return cmdhandler.cmdhandler(self, raw_string, callertype="object", session=session, **kwargs) diff --git a/evennia/server/inputfuncs.py b/evennia/server/inputfuncs.py index 6d852767af..9ed439ae13 100644 --- a/evennia/server/inputfuncs.py +++ b/evennia/server/inputfuncs.py @@ -25,7 +25,7 @@ from django.conf import settings from evennia.commands.cmdhandler import cmdhandler from evennia.accounts.models import AccountDB from evennia.utils.logger import log_err -from evennia.utils.utils import to_str, to_unicode +from evennia.utils.utils import to_str BrowserSessionStore = importlib.import_module(settings.SESSION_ENGINE).SessionStore @@ -176,7 +176,7 @@ def client_options(session, *args, **kwargs): def validate_encoding(val): # helper: change encoding try: - to_str(to_unicode("test-string"), encoding=val) + b"test-string".decode(val) except LookupError: raise RuntimeError("The encoding '|w%s|n' is invalid. " % val) return val diff --git a/evennia/server/sessionhandler.py b/evennia/server/sessionhandler.py index 4caf0c8b1e..8d58ca9a19 100644 --- a/evennia/server/sessionhandler.py +++ b/evennia/server/sessionhandler.py @@ -20,7 +20,7 @@ from django.conf import settings from evennia.commands.cmdhandler import CMD_LOGINSTART from evennia.utils.logger import log_trace from evennia.utils.utils import (variable_from_module, is_iter, - to_str, to_unicode, + to_str, make_iter, callables_from_module) from evennia.utils.inlinefuncs import parse_inlinefunc diff --git a/evennia/typeclasses/managers.py b/evennia/typeclasses/managers.py index 7bd2a406ad..45d9e40618 100644 --- a/evennia/typeclasses/managers.py +++ b/evennia/typeclasses/managers.py @@ -7,7 +7,7 @@ all Attributes and TypedObjects). import shlex from django.db.models import Q from evennia.utils import idmapper -from evennia.utils.utils import make_iter, variable_from_module, to_unicode +from evennia.utils.utils import make_iter, variable_from_module __all__ = ("TypedObjectManager", ) _GA = object.__getattribute__ @@ -494,7 +494,7 @@ class TypeclassManager(TypedObjectManager): """ # shlex splits by spaces unless escaped by quotes - querysplit = shlex.split(to_unicode(query, force_string=True)) + querysplit = shlex.split(query) queries, plustags, plusattrs, negtags, negattrs = [], [], [], [], [] for ipart, part in enumerate(querysplit): key, rest = part, "" diff --git a/evennia/utils/ansi.py b/evennia/utils/ansi.py index a63a11a2db..d55191c039 100644 --- a/evennia/utils/ansi.py +++ b/evennia/utils/ansi.py @@ -23,7 +23,7 @@ from django.conf import settings from evennia.utils import utils from evennia.utils import logger -from evennia.utils.utils import to_str, to_unicode +from evennia.utils.utils import to_str from future.utils import with_metaclass @@ -690,7 +690,7 @@ class ANSIString(with_metaclass(ANSIMeta, str)): decoded = True if not decoded: # Completely new ANSI String - clean_string = to_unicode(parser.parse_ansi(string, strip_ansi=True, mxp=True)) + clean_string = parser.parse_ansi(string, strip_ansi=True, mxp=True) string = parser.parse_ansi(string, xterm256=True, mxp=True) elif clean_string is not None: # We have an explicit clean string. diff --git a/evennia/utils/evform.py b/evennia/utils/evform.py index dfa5cb3729..7685bc22d2 100644 --- a/evennia/utils/evform.py +++ b/evennia/utils/evform.py @@ -140,7 +140,7 @@ from builtins import object, range import re import copy from evennia.utils.evtable import EvCell, EvTable -from evennia.utils.utils import all_from_module, to_str, to_unicode, is_iter +from evennia.utils.utils import all_from_module, to_str, is_iter from evennia.utils.ansi import ANSIString # non-valid form-identifying characters (which can thus be @@ -164,7 +164,7 @@ def _to_ansi(obj, regexable=False): elif is_iter(obj): return [_to_ansi(o) for o in obj] else: - return ANSIString(to_unicode(obj), regexable=regexable) + return ANSIString(obj, regexable=regexable) class EvForm(object): @@ -407,7 +407,7 @@ class EvForm(object): self.tablechar = tablechar[0] if len(tablechar) > 1 else tablechar # split into a list of list of lines. Form can be indexed with form[iy][ix] - self.raw_form = _to_ansi(to_unicode(datadict.get("FORM", "")).split("\n")) + self.raw_form = _to_ansi(datadict.get("FORM", "").split("\n")) # strip first line self.raw_form = self.raw_form[1:] if self.raw_form else self.raw_form diff --git a/evennia/utils/evtable.py b/evennia/utils/evtable.py index 10881e4bb0..95d2762459 100644 --- a/evennia/utils/evtable.py +++ b/evennia/utils/evtable.py @@ -120,7 +120,7 @@ from future.utils import listitems from django.conf import settings from textwrap import TextWrapper from copy import deepcopy, copy -from evennia.utils.utils import to_unicode, m_len, is_iter +from evennia.utils.utils import m_len, is_iter from evennia.utils.ansi import ANSIString _DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH @@ -137,7 +137,7 @@ def _to_ansi(obj): if is_iter(obj): return [_to_ansi(o) for o in obj] else: - return ANSIString(to_unicode(obj)) + return ANSIString(obj) _unicode = str diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index f083c19bcf..56514d90a7 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -96,7 +96,6 @@ def wrap(text, width=_DEFAULT_WIDTH, indent=0): """ if not text: return "" - text = to_unicode(text) indent = " " * indent return to_str(textwrap.fill(text, width, initial_indent=indent, subsequent_indent=indent)) @@ -149,14 +148,13 @@ def crop(text, width=_DEFAULT_WIDTH, suffix="[...]"): """ - utext = to_unicode(text) - ltext = len(utext) + ltext = len(text) if ltext <= width: return text else: lsuffix = len(suffix) - utext = utext[:width] if lsuffix >= width else "%s%s" % (utext[:width - lsuffix], suffix) - return to_str(utext) + text = text[:width] if lsuffix >= width else "%s%s" % (text[:width - lsuffix], suffix) + return to_str(text) def dedent(text): @@ -702,44 +700,6 @@ def latinify(unicode_string, default='?', pure_ascii=False): return ''.join(converted) -def to_unicode(obj, encoding='utf-8', force_string=False): - """ - This function is deprecated in the Python 3 version of Evennia and is - likely to be phased out in future releases. - - --- - This decodes a suitable object to the unicode format. - - Args: - obj (any): Object to decode to unicode. - encoding (str, optional): The encoding type to use for the - dedoding. - force_string (bool, optional): Always convert to string, no - matter what type `obj` is initially. - - Returns: - result (unicode or any): Will return a unicode object if input - was a string. If input was not a string, the original will be - returned unchanged unless `force_string` is also set. - - Notes: - One needs to encode the obj back to utf-8 before writing to disk - or printing. That non-string objects are let through without - conversion is important for e.g. Attributes. - - """ - - if isinstance(obj, (str, bytes, )): - return obj - - if force_string: - # some sort of other object. Try to - # convert it to a string representation. - obj = str(obj) - - return obj - - def to_str(obj, encoding='utf-8', force_string=False): """ This function is deprecated in the Python 3 version of Evennia and is From 131f7157c4616caf1378a990a4d87721e076655c Mon Sep 17 00:00:00 2001 From: Ryan Stein Date: Fri, 3 Nov 2017 12:45:24 -0400 Subject: [PATCH 029/493] Use a more robust method of validating an encoding. --- evennia/commands/default/account.py | 3 ++- evennia/commands/default/unloggedin.py | 3 ++- evennia/server/inputfuncs.py | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/evennia/commands/default/account.py b/evennia/commands/default/account.py index 85a25a5cac..73078ce468 100644 --- a/evennia/commands/default/account.py +++ b/evennia/commands/default/account.py @@ -21,6 +21,7 @@ method. Otherwise all text will be returned to all connected sessions. from builtins import range import time +from codecs import lookup as codecs_lookup from django.conf import settings from evennia.server.sessionhandler import SESSIONS from evennia.utils import utils, create, search, evtable @@ -533,7 +534,7 @@ class CmdOption(COMMAND_DEFAULT_CLASS): def validate_encoding(new_encoding): # helper: change encoding try: - b"test-string".decode(new_encoding) + codecs_lookup(new_encoding) except LookupError: raise RuntimeError("The encoding '|w%s|n' is invalid. " % new_encoding) return val diff --git a/evennia/commands/default/unloggedin.py b/evennia/commands/default/unloggedin.py index 149fd03824..784d1981ce 100644 --- a/evennia/commands/default/unloggedin.py +++ b/evennia/commands/default/unloggedin.py @@ -3,6 +3,7 @@ Commands that are available from the connect screen. """ import re import time +from codecs import lookup as codecs_lookup from collections import defaultdict from random import getrandbits from django.conf import settings @@ -481,7 +482,7 @@ class CmdUnconnectedEncoding(COMMAND_DEFAULT_CLASS): old_encoding = self.session.protocol_flags.get("ENCODING", None) encoding = self.args try: - utils.to_str(b"test-string".decode(encoding)) + codecs_lookup(encoding) except LookupError: string = "|rThe encoding '|w%s|r' is invalid. Keeping the previous encoding '|w%s|r'.|n"\ % (encoding, old_encoding) diff --git a/evennia/server/inputfuncs.py b/evennia/server/inputfuncs.py index 9ed439ae13..76953bc482 100644 --- a/evennia/server/inputfuncs.py +++ b/evennia/server/inputfuncs.py @@ -21,6 +21,7 @@ settings.INPUT_FUNC_MODULES. from future.utils import viewkeys import importlib +from codecs import lookup as codecs_lookup from django.conf import settings from evennia.commands.cmdhandler import cmdhandler from evennia.accounts.models import AccountDB @@ -176,7 +177,7 @@ def client_options(session, *args, **kwargs): def validate_encoding(val): # helper: change encoding try: - b"test-string".decode(val) + codecs_lookup(val) except LookupError: raise RuntimeError("The encoding '|w%s|n' is invalid. " % val) return val From 81806fe89b9c4a364d373020bd56ee1a396a5858 Mon Sep 17 00:00:00 2001 From: Vincent Le Goff Date: Thu, 28 Dec 2017 14:06:20 +0100 Subject: [PATCH 030/493] Add automatic escaping of the settings docstring for Windows' sake in py3k --- evennia/game_template/server/conf/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/game_template/server/conf/settings.py b/evennia/game_template/server/conf/settings.py index fa314c977a..d63a0e06bf 100644 --- a/evennia/game_template/server/conf/settings.py +++ b/evennia/game_template/server/conf/settings.py @@ -1,4 +1,4 @@ -""" +r""" Evennia settings file. The available options are found in the default settings file found From d0e632bb490cbae4f13d97cd9c14df453f7befd2 Mon Sep 17 00:00:00 2001 From: Henddher Pedroza Date: Sun, 2 Sep 2018 15:38:17 -0500 Subject: [PATCH 031/493] Puzzles System - first cut: PuzzlePartObject: typeclass for puzzle parts and results. PuzzleRecipeObject: typeclass to store prototypes of parts and results. PuzzleSystemCmdSet: commands to create, arm and resolve puzzles. --- evennia/contrib/puzzles.py | 472 +++++++++++++++++++++++++++++++++++++ 1 file changed, 472 insertions(+) create mode 100644 evennia/contrib/puzzles.py diff --git a/evennia/contrib/puzzles.py b/evennia/contrib/puzzles.py new file mode 100644 index 0000000000..6523d13ea6 --- /dev/null +++ b/evennia/contrib/puzzles.py @@ -0,0 +1,472 @@ +""" +Puzzles System - Provides a typeclass and commands for +objects that can be combined (i.e. 'use'd) to produce +new objects. + +Evennia contribution - Henddher 2018 + +A Puzzle is a recipe of what objects (aka parts) must +be combined by a player so a new set of objects +(aka results) are automatically created. + +Consider this simple Puzzle: + + orange, mango, yogurt, blender = fruit smoothie + +As a Builder: + + @create/drop orange + @create/drop mango + @create/drop yogurt + @create/drop blender + + @puzzle smoothie puzzle, orange, mango, yogurt, blender = fruit smoothie + ... + Puzzle smoothie puzzle (#1234) created successfuly. + + @destroy/force orange, mango, yogurt, blender + + @armpuzzle #1234 + Part orange is spawned at ... + Part mango is spawned at ... + .... + Puzzle smoothie puzzle (#1234) has been armed successfully + +As Player: + + use orange, mango, yogurt, blender + ... + Genius, you blended all fruits to create a yummy smoothie! + +Details: + +Puzzles are created from existing objects. The given +objects are introspected to create prototypes for the +puzzle parts. These prototypes become the puzzle recipe. +(See PuzzleRecipeObject and @puzzle command). + +At a later time, a Builder or a Script can arm the puzzle +and spawn all puzzle parts (PuzzlePartObject) in their +respective locations (See @armpuzzle). + +A regular player can collect the puzzle parts and combine +them (See use command). If player has specified +all pieces, the puzzle is considered solved and all +its puzzle parts are destroyed while the puzzle results +are spawened on their corresponding location. + +Installation: + +Add the PuzzleSystemCmdSet to all players. +Alternatively: + + @py self.cmdset.add('evennia.contrib.puzzles.PuzzleSystemCmdSet') + +""" + +import itertools +from random import choice +from django.conf import settings +from evennia import create_object +from evennia import CmdSet +from evennia import DefaultObject +from evennia import DefaultCharacter +from evennia import DefaultRoom +from evennia.commands.default.muxcommand import MuxCommand +from evennia.utils.utils import inherits_from +from evennia.utils import search, utils, logger +from evennia.utils.spawner import spawn + +# ----------- UTILITY FUNCTIONS ------------ + +def proto_def(obj, with_tags=True): + """ + Basic properties needed to spawn + and compare recipe with candidate part + """ + protodef = { + 'key': obj.key, + 'typeclass': 'evennia.contrib.puzzles.PuzzlePartObject', # FIXME: what if obj is another typeclass + 'desc': obj.db.desc, + 'location': obj.location, + # FIXME: Can tags be INVISIBLE? We don't want player to know an object belongs to a puzzle + 'tags': [(_PUZZLES_TAG_MEMBER, _PUZZLES_TAG_CATEGORY)], + } + if not with_tags: + del(protodef['tags']) + return protodef + +# ------------------------------------------ + +# Tag used by puzzles +_PUZZLES_TAG_CATEGORY = 'puzzles' +_PUZZLES_TAG_RECIPE = 'puzzle_recipe' +# puzzle part and puzzle result +_PUZZLES_TAG_MEMBER = 'puzzle_member' + + +class PuzzlePartObject(DefaultObject): + """ + Puzzle Part, typically used by @armpuzzle command + """ + + def mark_as_puzzle_member(self, puzzle_name): + """ + Marks this object as a member of puzzle named + puzzle_name + """ + # FIXME: if multiple puzzles have the same + # puzzle_name, their ingredients may be + # combined but leave other parts orphan + # Similarly, if a puzzle_name were changed, + # its parts will become orphan + # Perhaps we should use #dbref but that will + # force specific parts to be combined + self.db.puzzle_name = puzzle_name + + +class PuzzleRecipeObject(DefaultObject): + """ + Definition of a Puzzle Recipe + """ + + def save_recipe(self, puzzle_name, parts, results): + self.db.puzzle_name = puzzle_name + self.db.parts = tuple(parts) + self.db.results = tuple(results) + self.tags.add(_PUZZLES_TAG_RECIPE, category=_PUZZLES_TAG_CATEGORY) + + +class CmdCreatePuzzleRecipe(MuxCommand): + """ + Creates a puzzle recipe. + + Each part and result must exist and be placed in their corresponding location. + All parts and results are left intact. Caller must explicitly + destroy them. + + Usage: + @puzzle name,] = + """ + + key = '@puzzle' + aliases = '@puzzlerecipe' + locks = 'cmd:perm(puzzle) or perm(Builder)' + help_category = 'Puzzles' + + def func(self): + caller = self.caller + + if len(self.lhslist) < 2 \ + or not self.rhs: + string = "Usage: @puzzle name, = " + caller.msg(string) + return + + puzzle_name = self.lhslist[0] + + def is_valid_obj_location(obj): + valid = True + # Valid locations are: room, ... + # TODO: other valid locations must be added here + # Certain locations can be handled accordingly: e.g, + # a part is located in a character's inventory, + # perhaps will translate into the player character + # having the part in his/her inventory while being + # located in the same room where the builder was + # located. + # Parts and results may have different valid locations + # TODO: handle contents of a given part + if not inherits_from(obj.location, settings.BASE_ROOM_TYPECLASS): + caller.msg('Invalid location for %s' % (obj.key)) + valid = False + return valid + + def is_valid_part_location(part): + return is_valid_obj_location(part) + + def is_valid_result_location(part): + return is_valid_obj_location(part) + + parts = [] + for objname in self.lhslist[1:]: + obj = caller.search(objname) + if not obj: + return + if not is_valid_part_location(obj): + return + parts.append(obj) + + results = [] + for objname in self.rhslist: + obj = caller.search(objname) + if not obj: + return + if not is_valid_result_location(obj): + return + results.append(obj) + + for part in parts: + caller.msg('Part %s(%s)' % (part.name, part.dbref)) + + for result in results: + caller.msg('Result %s(%s)' % (result.name, result.dbref)) + + proto_parts = [proto_def(obj) for obj in parts] + proto_results = [proto_def(obj) for obj in results] + + puzzle = create_object(PuzzleRecipeObject, key=puzzle_name) + puzzle.save_recipe(puzzle_name, proto_parts, proto_results) + + caller.msg( + "Puzzle |y'%s' |w%s(%s)|n has been created |gsuccessfully|n." + % (puzzle.db.puzzle_name, puzzle.name, puzzle.dbref)) + caller.msg( + 'You may now dispose all parts and results. ' + 'Typically, results and parts are useless afterwards.\n' + 'You are now able to arm this puzzle using Builder command:\n' + ' @armpuzzle \n\n' + 'Or programmatically.\n' + ) + + # FIXME: puzzle recipe object exists but it has no location + # should we create a PuzzleLibrary where all puzzles are + # kept and cannot be reached by players? + + +class CmdArmPuzzle(MuxCommand): + """ + Arms a puzzle by spawning all its parts + """ + + key = '@armpuzzle' + # FIXME: permissions for scripts? + locks = 'cmd:perm(armpuzzle) or perm(Builder)' + help_category = 'Puzzles' + + def func(self): + caller = self.caller + + if self.args is None or not utils.dbref(self.args): + caller.msg("A puzzle recipe's #dbref must be specified") + return + + puzzle = caller.search(self.args, global_search=True) + if not puzzle or not inherits_from(puzzle, PuzzleRecipeObject): + return + + caller.msg( + "Puzzle Recipe %s(%s) '%s' found.\nSpawning %d parts ..." % ( + puzzle.name, puzzle.dbref, puzzle.db.puzzle_name, len(puzzle.db.parts))) + + for proto_part in puzzle.db.parts: + # caller.msg('Protopart %r %r' % (proto_part, type(proto_part))) + part = spawn(proto_part)[0] + caller.msg("Part %s(%s) spawned and placed at %s(%s)" % (part.name, part.dbref, part.location, part.location.dbref)) + part.mark_as_puzzle_member(puzzle.db.puzzle_name) + + caller.msg("Puzzle armed |gsuccessfully|n.") + + +class CmdUsePuzzleParts(MuxCommand): + """ + Searches for all puzzles whose parts + match the given set of objects. If + there are matching puzzles, the result + objects are spawned in their corresponding + location if all parts have been passed in. + + Usage: + use ] + """ + + # TODO: consider allowing builder to provide + # messages and "hooks" that can be displayed + # and/or fired whenever the resolver of the puzzle + # enters the location where a result was spawned + + key = 'use' + aliases = 'combine' + locks = 'cmd:pperm(use) or pperm(Player)' + help_category = 'Puzzles' + + def func(self): + caller = self.caller + + if not self.lhs: + caller.msg('Use what?') + return + + many = 'these' if len(self.lhslist) > 1 else 'this' + + # either all are parts, or abort finding matching puzzles + parts = [] + partnames = self.lhslist[:] + for partname in partnames: + part = caller.search( + partname, + multimatch_string='Which %s. There are many.\n' % (partname), + nofound_string='There is no %s around.' % (partname) + ) + + if not part: + return + + if not part.tags.get(_PUZZLES_TAG_MEMBER, category=_PUZZLES_TAG_CATEGORY) \ + or not inherits_from(part, PuzzlePartObject): + + # not a puzzle part ... abort + caller.msg('You have no idea how %s can be used' % (many)) + return + + # a valid part + parts.append(part) + + # Create lookup dict + parts_dict = dict((part.dbref, part) for part in parts) + + # Group parts by their puzzle name + puzzle_ingredients = dict() + for part in parts: + puzzle_name = part.db.puzzle_name + if puzzle_name not in puzzle_ingredients: + puzzle_ingredients[puzzle_name] = [] + puzzle_ingredients[puzzle_name].append( + (part.dbref, proto_def(part, with_tags=False)) + ) + + # Find all puzzles by puzzle name + # FIXME: we rely on obj.db.puzzle_name which is visible and may be cnaged afterwards. Can we lock it and hide it? + puzzles = [] + for puzzle_name, parts in puzzle_ingredients.items(): + _puzzles = caller.search( + puzzle_name, + typeclass=[PuzzleRecipeObject], + attribute_name='puzzle_name', + quiet=True, + exact=True, + global_search=True) + if not _puzzles: + continue + else: + puzzles.extend(_puzzles) + + # Create lookup dict + puzzles_dict = dict((puzzle.dbref, puzzle) for puzzle in puzzles) + + # Check if parts can be combined to solve a puzzle + matched_puzzles = dict() + for puzzle in puzzles: + puzzleparts = puzzle.db.parts[:] + parts = puzzle_ingredients[puzzle.db.puzzle_name][:] + pz = 0 + p = 0 + matched_dbrefparts = set() + while pz < len(puzzleparts) and p < len(parts): + puzzlepart = puzzleparts[pz] + if 'tags' in puzzlepart: + # remove 'tags' as they will prevent equality + del(puzzlepart['tags']) + dbref, part = parts[p] + if part == puzzlepart: + pz += 1 + matched_dbrefparts.add(dbref) + else: + pass + p += 1 + else: + if len(puzzleparts) == len(matched_dbrefparts): + matched_puzzles[puzzle.dbref] = matched_dbrefparts + + if len(matched_puzzles) == 0: + # FIXME: Add more random messages + # random part falls and lands on your feet + # random part hits you square on the face + caller.msg("As you try to utilize %s, nothing happens." % (many)) + return + + puzzletuples = sorted(matched_puzzles.items(), key=lambda t: len(t[1]), reverse=True) + + # sort all matched puzzles and pick largest one(s) + puzzledbref, matched_dbrefparts = puzzletuples[0] + nparts = len(matched_dbrefparts) + largest_puzzles = list(itertools.takewhile(lambda t: len(t[1]) == nparts, puzzletuples)) + + # if there are more than one, let user pick + if len(largest_puzzles) > 1: + # FIXME: pick a random one or let user choose? + caller.msg( + 'Your gears start turning and a bunch of ideas come to your mind ...\n%s' % ( + ' ...\n'.join([lp.db.puzzle_name for lp in largest_puzzles])) + ) + puzzle = choice(largest_puzzles) + caller.msg("You try %s ..." % (puzzle.db.puzzle_name)) + + # got one, spawn its results + puzzle = puzzles_dict[puzzledbref] + # FIXME: DRY with parts + for proto_result in puzzle.db.results: + result = spawn(proto_result)[0] + result.mark_as_puzzle_member(puzzle.db.puzzle_name) + # FIXME: add 'ramdon' messages: + # Hmmm ... did I search result.location? + # What was that? ... I heard something in result.location? + # Eureka! you built a result + + # Destroy all parts used + for dbref in matched_dbrefparts: + parts_dict[dbref].delete() + + # FIXME: Add random messages + # You are a genius ... no matter what your 2nd grade teacher told you + # You hear thunders and a cloud of dust raises leaving + caller.msg("Puzzle solved |gsuccessfully|n.") + + +class CmdListPuzzleRecipes(MuxCommand): + """ + Searches for all puzzle recipes + + Usage: + @lspuzzlerecipes + """ + + key = '@lspuzzlerecipes' + locks = 'cmd:perm(lspuzzlerecipes) or perm(Builder)' + help_category = 'Puzzles' + + def func(self): + caller = self.caller + # TODO: use @tags/search puzzle_recipe : puzzles + + +class CmdListArmedPuzzles(MuxCommand): + """ + Searches for all armed puzzles + + Usage: + @lsarmedpuzzles + """ + + key = '@lsarmedpuzzles' + locks = 'cmd:perm(lsarmedpuzzles) or perm(Builder)' + help_category = 'Puzzles' + + def func(self): + caller = self.caller + # TODO: use @tags/search puzzle_member : puzzles + + +class PuzzleSystemCmdSet(CmdSet): + """ + CmdSet to create, arm and resolve Puzzles + + Add with @py self.cmdset.add("evennia.contrib.puzzles.PuzzlesCmdSet") + """ + + def at_cmdset_creation(self): + super(PuzzleSystemCmdSetCmdSet, self).at_cmdset_creation() + + self.add(CmdCreatePuzzleRecipe()) + self.add(CmdArmPuzzle()) + self.add(CmdUsePuzzleParts()) From 11099f5b357e35e3e4a522136260e6e473226d59 Mon Sep 17 00:00:00 2001 From: Henddher Pedroza Date: Sun, 2 Sep 2018 16:46:57 -0500 Subject: [PATCH 032/493] typo in classname --- evennia/contrib/puzzles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/contrib/puzzles.py b/evennia/contrib/puzzles.py index 6523d13ea6..213cd7793c 100644 --- a/evennia/contrib/puzzles.py +++ b/evennia/contrib/puzzles.py @@ -465,7 +465,7 @@ class PuzzleSystemCmdSet(CmdSet): """ def at_cmdset_creation(self): - super(PuzzleSystemCmdSetCmdSet, self).at_cmdset_creation() + super(PuzzleSystemCmdSet, self).at_cmdset_creation() self.add(CmdCreatePuzzleRecipe()) self.add(CmdArmPuzzle()) From 280dd3f4eb707dd517123209cee8639797f80148 Mon Sep 17 00:00:00 2001 From: Henddher Pedroza Date: Sun, 2 Sep 2018 16:46:57 -0500 Subject: [PATCH 033/493] When multiple puzzles are matched, show their names to the caller and then randomly pick one --- evennia/contrib/puzzles.py | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/evennia/contrib/puzzles.py b/evennia/contrib/puzzles.py index 6523d13ea6..2e730e9038 100644 --- a/evennia/contrib/puzzles.py +++ b/evennia/contrib/puzzles.py @@ -85,6 +85,7 @@ def proto_def(obj, with_tags=True): and compare recipe with candidate part """ protodef = { + # FIXME: Don't we need to honor ALL properties? locks, perms, etc. 'key': obj.key, 'typeclass': 'evennia.contrib.puzzles.PuzzlePartObject', # FIXME: what if obj is another typeclass 'desc': obj.db.desc, @@ -260,7 +261,6 @@ class CmdArmPuzzle(MuxCommand): puzzle.name, puzzle.dbref, puzzle.db.puzzle_name, len(puzzle.db.parts))) for proto_part in puzzle.db.parts: - # caller.msg('Protopart %r %r' % (proto_part, type(proto_part))) part = spawn(proto_part)[0] caller.msg("Part %s(%s) spawned and placed at %s(%s)" % (part.name, part.dbref, part.location, part.location.dbref)) part.mark_as_puzzle_member(puzzle.db.puzzle_name) @@ -351,6 +351,8 @@ class CmdUsePuzzleParts(MuxCommand): else: puzzles.extend(_puzzles) + logger.log_info("PUZZLES %r" % ([p.dbref for p in puzzles])) + # Create lookup dict puzzles_dict = dict((puzzle.dbref, puzzle) for puzzle in puzzles) @@ -371,8 +373,6 @@ class CmdUsePuzzleParts(MuxCommand): if part == puzzlepart: pz += 1 matched_dbrefparts.add(dbref) - else: - pass p += 1 else: if len(puzzleparts) == len(matched_dbrefparts): @@ -387,27 +387,33 @@ class CmdUsePuzzleParts(MuxCommand): puzzletuples = sorted(matched_puzzles.items(), key=lambda t: len(t[1]), reverse=True) + logger.log_info("MATCHED PUZZLES %r" % (puzzletuples)) + # sort all matched puzzles and pick largest one(s) puzzledbref, matched_dbrefparts = puzzletuples[0] nparts = len(matched_dbrefparts) + puzzle = puzzles_dict[puzzledbref] largest_puzzles = list(itertools.takewhile(lambda t: len(t[1]) == nparts, puzzletuples)) - # if there are more than one, let user pick + # if there are more than one, ... if len(largest_puzzles) > 1: # FIXME: pick a random one or let user choose? + # FIXME: do we show the puzzle name or something else? caller.msg( 'Your gears start turning and a bunch of ideas come to your mind ...\n%s' % ( - ' ...\n'.join([lp.db.puzzle_name for lp in largest_puzzles])) + ' ...\n'.join([puzzles_dict[lp[0]].db.puzzle_name for lp in largest_puzzles])) ) - puzzle = choice(largest_puzzles) + puzzletuple = choice(largest_puzzles) + puzzle = puzzles_dict[puzzletuple[0]] caller.msg("You try %s ..." % (puzzle.db.puzzle_name)) # got one, spawn its results - puzzle = puzzles_dict[puzzledbref] # FIXME: DRY with parts + result_names = [] for proto_result in puzzle.db.results: result = spawn(proto_result)[0] result.mark_as_puzzle_member(puzzle.db.puzzle_name) + result_names.append(result.name) # FIXME: add 'ramdon' messages: # Hmmm ... did I search result.location? # What was that? ... I heard something in result.location? @@ -420,7 +426,16 @@ class CmdUsePuzzleParts(MuxCommand): # FIXME: Add random messages # You are a genius ... no matter what your 2nd grade teacher told you # You hear thunders and a cloud of dust raises leaving - caller.msg("Puzzle solved |gsuccessfully|n.") + result_names = ', '.join(result_names) + caller.msg( + "You are a |wG|re|wn|ri|wu|rs|n!!!\nYou just created %s" % ( + result_names + )) + caller.location.msg_contents( + "|c%s|n performs some kind of tribal dance" + " and seems to create |y%s|n from thin air" % ( + caller, result_names), exclude=(caller,) + ) class CmdListPuzzleRecipes(MuxCommand): @@ -465,7 +480,7 @@ class PuzzleSystemCmdSet(CmdSet): """ def at_cmdset_creation(self): - super(PuzzleSystemCmdSetCmdSet, self).at_cmdset_creation() + super(PuzzleSystemCmdSet, self).at_cmdset_creation() self.add(CmdCreatePuzzleRecipe()) self.add(CmdArmPuzzle()) From 17c07bb47eb98c99bbb977d9e2a32cdd8e1252d5 Mon Sep 17 00:00:00 2001 From: Henddher Pedroza Date: Sun, 2 Sep 2018 22:34:17 -0500 Subject: [PATCH 034/493] Addition of CmdListPuzzleRecipes and CmdListArmedPuzzles --- evennia/contrib/puzzles.py | 61 ++++++++++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 9 deletions(-) diff --git a/evennia/contrib/puzzles.py b/evennia/contrib/puzzles.py index 2e730e9038..12fb83d03b 100644 --- a/evennia/contrib/puzzles.py +++ b/evennia/contrib/puzzles.py @@ -77,6 +77,12 @@ from evennia.utils.utils import inherits_from from evennia.utils import search, utils, logger from evennia.utils.spawner import spawn +# Tag used by puzzles +_PUZZLES_TAG_CATEGORY = 'puzzles' +_PUZZLES_TAG_RECIPE = 'puzzle_recipe' +# puzzle part and puzzle result +_PUZZLES_TAG_MEMBER = 'puzzle_member' + # ----------- UTILITY FUNCTIONS ------------ def proto_def(obj, with_tags=True): @@ -99,13 +105,6 @@ def proto_def(obj, with_tags=True): # ------------------------------------------ -# Tag used by puzzles -_PUZZLES_TAG_CATEGORY = 'puzzles' -_PUZZLES_TAG_RECIPE = 'puzzle_recipe' -# puzzle part and puzzle result -_PUZZLES_TAG_MEMBER = 'puzzle_member' - - class PuzzlePartObject(DefaultObject): """ Puzzle Part, typically used by @armpuzzle command @@ -452,7 +451,30 @@ class CmdListPuzzleRecipes(MuxCommand): def func(self): caller = self.caller - # TODO: use @tags/search puzzle_recipe : puzzles + + recipes = search.search_tag( + _PUZZLES_TAG_RECIPE, category=_PUZZLES_TAG_CATEGORY) + + div = "-" * 60 + text = [div] + msgf_recipe = "Puzzle |y'%s' %s(%s)|n" + msgf_item = "%2s|c%15s|n: |w%s|n" + for recipe in recipes: + text.append(msgf_recipe % (recipe.db.puzzle_name, recipe.name, recipe.dbref)) + text.append('Parts') + for protopart in recipe.db.parts[:]: + mark = '-' + for k, v in protopart.items(): + text.append(msgf_item % (mark, k, v)) + mark = '' + text.append('Results') + for protoresult in recipe.db.results[:]: + mark = '-' + for k, v in protoresult.items(): + text.append(msgf_item % (mark, k, v)) + mark = '' + text.append(div) + caller.msg('\n'.join(text)) class CmdListArmedPuzzles(MuxCommand): @@ -469,7 +491,26 @@ class CmdListArmedPuzzles(MuxCommand): def func(self): caller = self.caller - # TODO: use @tags/search puzzle_member : puzzles + + armed_puzzles = search.search_tag( + _PUZZLES_TAG_MEMBER, category=_PUZZLES_TAG_CATEGORY) + + armed_puzzles = dict((k, list(g)) for k, g in itertools.groupby( + armed_puzzles, + lambda ap: ap.db.puzzle_name)) + + div = '-' * 60 + msgf_pznm = "Puzzle name: |y%s|n" + msgf_item = "|m%25s|w(%s)|n at |c%25s|w(%s)|n" + text = [div] + for pzname, items in armed_puzzles.items(): + text.append(msgf_pznm % (pzname)) + for item in items: + text.append(msgf_item % ( + item.name, item.dbref, + item.location.name, item.location.dbref)) + text.append(div) + caller.msg('\n'.join(text)) class PuzzleSystemCmdSet(CmdSet): @@ -484,4 +525,6 @@ class PuzzleSystemCmdSet(CmdSet): self.add(CmdCreatePuzzleRecipe()) self.add(CmdArmPuzzle()) + self.add(CmdListPuzzleRecipes()) + self.add(CmdListArmedPuzzles()) self.add(CmdUsePuzzleParts()) From 3a6f693c969c5c5ab7089a98d891172e5aa7a925 Mon Sep 17 00:00:00 2001 From: Henddher Pedroza Date: Sun, 2 Sep 2018 22:48:42 -0500 Subject: [PATCH 035/493] Documentation corrections and clarifications --- evennia/contrib/puzzles.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/evennia/contrib/puzzles.py b/evennia/contrib/puzzles.py index 12fb83d03b..70bb92c108 100644 --- a/evennia/contrib/puzzles.py +++ b/evennia/contrib/puzzles.py @@ -19,31 +19,34 @@ As a Builder: @create/drop mango @create/drop yogurt @create/drop blender + @create/drop fruit smoothie - @puzzle smoothie puzzle, orange, mango, yogurt, blender = fruit smoothie + @puzzle smoothie, orange, mango, yogurt, blender = fruit smoothie ... - Puzzle smoothie puzzle (#1234) created successfuly. + Puzzle smoothie(#1234) created successfuly. - @destroy/force orange, mango, yogurt, blender + @destroy/force orange, mango, yogurt, blender, fruit smoothie @armpuzzle #1234 Part orange is spawned at ... Part mango is spawned at ... .... - Puzzle smoothie puzzle (#1234) has been armed successfully + Puzzle smoothie(#1234) has been armed successfully As Player: use orange, mango, yogurt, blender ... - Genius, you blended all fruits to create a yummy smoothie! + Genius, you blended all fruits to create a fruit smoothie! Details: Puzzles are created from existing objects. The given objects are introspected to create prototypes for the -puzzle parts. These prototypes become the puzzle recipe. -(See PuzzleRecipeObject and @puzzle command). +puzzle parts and results. These prototypes become the +puzzle recipe. (See PuzzleRecipeObject and @puzzle +command). Once the recipe is created, all parts and result +can be disposed (i.e. destroyed). At a later time, a Builder or a Script can arm the puzzle and spawn all puzzle parts (PuzzlePartObject) in their From 5aeb0c50d72502995c2b81c361ee1ed3683fb11b Mon Sep 17 00:00:00 2001 From: Henddher Pedroza Date: Mon, 3 Sep 2018 20:54:33 -0500 Subject: [PATCH 036/493] Tests for puzzles --- evennia/contrib/puzzles.py | 3 +- evennia/contrib/tests.py | 112 +++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 2 deletions(-) diff --git a/evennia/contrib/puzzles.py b/evennia/contrib/puzzles.py index 70bb92c108..59b780af2d 100644 --- a/evennia/contrib/puzzles.py +++ b/evennia/contrib/puzzles.py @@ -69,7 +69,6 @@ Alternatively: import itertools from random import choice -from django.conf import settings from evennia import create_object from evennia import CmdSet from evennia import DefaultObject @@ -180,7 +179,7 @@ class CmdCreatePuzzleRecipe(MuxCommand): # located. # Parts and results may have different valid locations # TODO: handle contents of a given part - if not inherits_from(obj.location, settings.BASE_ROOM_TYPECLASS): + if not inherits_from(obj.location, DefaultRoom): caller.msg('Invalid location for %s' % (obj.key)) valid = False return valid diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 2a337d2065..1210486411 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -1170,3 +1170,115 @@ class TestRandomStringGenerator(EvenniaTest): # We can't generate one more with self.assertRaises(random_string_generator.ExhaustedGenerator): SIMPLE_GENERATOR.get() + + +# Test of the Puzzles module + +from evennia.contrib import puzzles +from evennia.utils import search + +class TestPuzzles(CommandTest): + + def setUp(self): + super(TestPuzzles, self).setUp() + self.stone = create_object(key='stone', location=self.char1.location) + self.flint = create_object(key='flint', location=self.char1.location) + self.fire = create_object(key='fire', location=self.char1.location) + + def _assert_msg_matched(self, msg, regexs, re_flags=0): + matches = [] + for regex in regexs: + m = re.search(regex, msg, re_flags) + self.assertIsNotNone(m, "%r didn't match %r" % (regex, msg)) + matches.append(m) + return matches + + def _assert_recipe(self, name, parts, results, and_destroy_it=True): + + def _keys(items): + return [item['key'] for item in items] + + recipes = search.search_tag('', category=puzzles._PUZZLES_TAG_CATEGORY) + self.assertEqual(1, len(recipes)) + self.assertEqual(name, recipes[0].db.puzzle_name) + self.assertEqual(parts, _keys(recipes[0].db.parts)) + self.assertEqual(results, _keys(recipes[0].db.results)) + self.assertEqual( + puzzles._PUZZLES_TAG_RECIPE, + recipes[0].tags.get(category=puzzles._PUZZLES_TAG_CATEGORY) + ) + if and_destroy_it: + recipes[0].delete() + + def _assert_no_recipes(self): + self.assertEqual( + 0, + len(search.search_tag('', category=puzzles._PUZZLES_TAG_CATEGORY)) + ) + + def test_cmd_use(self): + def _use(cmdstr, msg): + self.call(puzzles.CmdUsePuzzleParts(), cmdstr, msg, caller=self.char1) + + _use('', 'Use what?') + _use('stone', 'You have no idea how this can be used') + _use('stone flint', 'There is no stone flint around.') + _use('stone, flint', 'You have no idea how these can be used') + + def test_cmd_puzzle(self): + self._assert_no_recipes() + + # bad syntax + def _bad_syntax(cmdstr): + self.call( + puzzles.CmdCreatePuzzleRecipe(), + cmdstr, + 'Usage: @puzzle name, = ', + caller=self.char1 + ) + + _bad_syntax('') + _bad_syntax('=') + _bad_syntax('nothing =') + _bad_syntax('= nothing') + _bad_syntax('nothing') + _bad_syntax(',nothing') + _bad_syntax('name, nothing') + _bad_syntax('name, nothing =') + # _bad_syntax(', = ,') # FIXME: got: Could not find ''. + + self._assert_no_recipes() + + # good recipes + def _good_recipe(name, parts, results): + regexs = [] + for p in parts: + regexs.append(r'^Part %s\(#\d+\)$' % (p)) + for r in results: + regexs.append(r'^Result %s\(#\d+\)$' % (r)) + regexs.append(r"^Puzzle '%s' %s\(#\d+\) has been created successfully.$" % (name, name)) + lhs = [name] + parts + cmdstr = ','.join(lhs) + '=' + ','.join(results) + msg = self.call( + puzzles.CmdCreatePuzzleRecipe(), + cmdstr, + caller=self.char1 + ) + matches = self._assert_msg_matched(msg, regexs, re_flags=re.MULTILINE | re.DOTALL) + self._assert_recipe(name, parts, results) + + _good_recipe('makefire', ['stone', 'flint'], ['fire', 'stone', 'flint']) + _good_recipe('hot stones', ['stone', 'fire'], ['stone', 'fire']) + _good_recipe('hot stones', ['stone', 'fire'], ['stone', 'fire']) + + # bad recipes + def _bad_recipe(name, parts, results, fail_regex): + with self.assertRaisesRegexp(AssertionError, fail_regex): + _good_recipe(name, parts, results) + self.assert_no_recipes() + + _bad_recipe('name', ['nothing'], ['neither'], r"Could not find 'nothing'.") + _bad_recipe('name', ['stone'], ['nothing'], r"Could not find 'nothing'.") + # _bad_recipe('', ['stone', 'fire'], ['stone', 'fire'], '') # FIXME: no name becomes '' #N(#N) + + self._assert_no_recipes() From c3b8614526996f8a216f5a1bb31eb534eecbfb07 Mon Sep 17 00:00:00 2001 From: Henddher Pedroza Date: Sun, 9 Sep 2018 22:43:26 -0500 Subject: [PATCH 037/493] PuzzleRecipe as DefaultScript; not as DefaultObject. Misc tests --- evennia/contrib/puzzles.py | 35 ++++++------ evennia/contrib/tests.py | 112 +++++++++++++++++++++++++++---------- 2 files changed, 100 insertions(+), 47 deletions(-) diff --git a/evennia/contrib/puzzles.py b/evennia/contrib/puzzles.py index 59b780af2d..0a2a4cb447 100644 --- a/evennia/contrib/puzzles.py +++ b/evennia/contrib/puzzles.py @@ -44,7 +44,7 @@ Details: Puzzles are created from existing objects. The given objects are introspected to create prototypes for the puzzle parts and results. These prototypes become the -puzzle recipe. (See PuzzleRecipeObject and @puzzle +puzzle recipe. (See PuzzleRecipe and @puzzle command). Once the recipe is created, all parts and result can be disposed (i.e. destroyed). @@ -69,9 +69,10 @@ Alternatively: import itertools from random import choice -from evennia import create_object +from evennia import create_object, create_script from evennia import CmdSet from evennia import DefaultObject +from evennia import DefaultScript from evennia import DefaultCharacter from evennia import DefaultRoom from evennia.commands.default.muxcommand import MuxCommand @@ -127,7 +128,7 @@ class PuzzlePartObject(DefaultObject): self.db.puzzle_name = puzzle_name -class PuzzleRecipeObject(DefaultObject): +class PuzzleRecipe(DefaultScript): """ Definition of a Puzzle Recipe """ @@ -217,7 +218,7 @@ class CmdCreatePuzzleRecipe(MuxCommand): proto_parts = [proto_def(obj) for obj in parts] proto_results = [proto_def(obj) for obj in results] - puzzle = create_object(PuzzleRecipeObject, key=puzzle_name) + puzzle = create_script(PuzzleRecipe, key=puzzle_name) puzzle.save_recipe(puzzle_name, proto_parts, proto_results) caller.msg( @@ -253,10 +254,12 @@ class CmdArmPuzzle(MuxCommand): caller.msg("A puzzle recipe's #dbref must be specified") return - puzzle = caller.search(self.args, global_search=True) - if not puzzle or not inherits_from(puzzle, PuzzleRecipeObject): + puzzle = search.search_script(self.args) + if not puzzle or not inherits_from(puzzle[0], PuzzleRecipe): + caller.msg('Invalid puzzle %r' % (self.args)) return + puzzle = puzzle[0] caller.msg( "Puzzle Recipe %s(%s) '%s' found.\nSpawning %d parts ..." % ( puzzle.name, puzzle.dbref, puzzle.db.puzzle_name, len(puzzle.db.parts))) @@ -336,17 +339,16 @@ class CmdUsePuzzleParts(MuxCommand): (part.dbref, proto_def(part, with_tags=False)) ) + # Find all puzzles by puzzle name # FIXME: we rely on obj.db.puzzle_name which is visible and may be cnaged afterwards. Can we lock it and hide it? puzzles = [] for puzzle_name, parts in puzzle_ingredients.items(): - _puzzles = caller.search( - puzzle_name, - typeclass=[PuzzleRecipeObject], - attribute_name='puzzle_name', - quiet=True, - exact=True, - global_search=True) + _puzzles = search.search_script_attribute( + key='puzzle_name', + value=puzzle_name + ) + _puzzles = list(filter(lambda p: isinstance(p, PuzzleRecipe), _puzzles)) if not _puzzles: continue else: @@ -356,12 +358,11 @@ class CmdUsePuzzleParts(MuxCommand): # Create lookup dict puzzles_dict = dict((puzzle.dbref, puzzle) for puzzle in puzzles) - # Check if parts can be combined to solve a puzzle matched_puzzles = dict() for puzzle in puzzles: - puzzleparts = puzzle.db.parts[:] - parts = puzzle_ingredients[puzzle.db.puzzle_name][:] + puzzleparts = list(sorted(puzzle.db.parts[:], key=lambda p: p['key'])) + parts = list(sorted(puzzle_ingredients[puzzle.db.puzzle_name][:], key=lambda p: p[1]['key'])) pz = 0 p = 0 matched_dbrefparts = set() @@ -454,7 +455,7 @@ class CmdListPuzzleRecipes(MuxCommand): def func(self): caller = self.caller - recipes = search.search_tag( + recipes = search.search_script_tag( _PUZZLES_TAG_RECIPE, category=_PUZZLES_TAG_CATEGORY) div = "-" * 60 diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 1210486411..1d8b2f2324 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -1198,7 +1198,7 @@ class TestPuzzles(CommandTest): def _keys(items): return [item['key'] for item in items] - recipes = search.search_tag('', category=puzzles._PUZZLES_TAG_CATEGORY) + recipes = search.search_script_tag('', category=puzzles._PUZZLES_TAG_CATEGORY) self.assertEqual(1, len(recipes)) self.assertEqual(name, recipes[0].db.puzzle_name) self.assertEqual(parts, _keys(recipes[0].db.parts)) @@ -1207,8 +1207,10 @@ class TestPuzzles(CommandTest): puzzles._PUZZLES_TAG_RECIPE, recipes[0].tags.get(category=puzzles._PUZZLES_TAG_CATEGORY) ) + recipe_dbref = recipes[0].dbref if and_destroy_it: recipes[0].delete() + return recipe_dbref if not and_destroy_it else None def _assert_no_recipes(self): self.assertEqual( @@ -1216,14 +1218,41 @@ class TestPuzzles(CommandTest): len(search.search_tag('', category=puzzles._PUZZLES_TAG_CATEGORY)) ) - def test_cmd_use(self): - def _use(cmdstr, msg): - self.call(puzzles.CmdUsePuzzleParts(), cmdstr, msg, caller=self.char1) + # good recipes + def _good_recipe(self, name, parts, results, and_destroy_it=True): + regexs = [] + for p in parts: + regexs.append(r'^Part %s\(#\d+\)$' % (p)) + for r in results: + regexs.append(r'^Result %s\(#\d+\)$' % (r)) + regexs.append(r"^Puzzle '%s' %s\(#\d+\) has been created successfully.$" % (name, name)) + lhs = [name] + parts + cmdstr = ','.join(lhs) + '=' + ','.join(results) + msg = self.call( + puzzles.CmdCreatePuzzleRecipe(), + cmdstr, + caller=self.char1 + ) + matches = self._assert_msg_matched(msg, regexs, re_flags=re.MULTILINE | re.DOTALL) + recipe_dbref = self._assert_recipe(name, parts, results, and_destroy_it) + return recipe_dbref - _use('', 'Use what?') - _use('stone', 'You have no idea how this can be used') - _use('stone flint', 'There is no stone flint around.') - _use('stone, flint', 'You have no idea how these can be used') + def _arm(self, recipe_dbref): + msg = self.call( + puzzles.CmdArmPuzzle(), + recipe_dbref, + caller=self.char1 + ) + print(msg) + # TODO: add regex for parts and whatnot + # similar to _good_recipe + ''' + Puzzle Recipe makefire(#2) 'makefire' found. +Spawning 2 parts ... +Part stone(#11) spawned and placed at Room(#1) +Part flint(#12) spawned and placed at Room(#1) +Puzzle armed successfully.''' + self.assertIsNotNone(re.match(r"Puzzle Recipe .* found.*Puzzle armed successfully.", msg, re.MULTILINE | re.DOTALL)) def test_cmd_puzzle(self): self._assert_no_recipes() @@ -1249,32 +1278,14 @@ class TestPuzzles(CommandTest): self._assert_no_recipes() - # good recipes - def _good_recipe(name, parts, results): - regexs = [] - for p in parts: - regexs.append(r'^Part %s\(#\d+\)$' % (p)) - for r in results: - regexs.append(r'^Result %s\(#\d+\)$' % (r)) - regexs.append(r"^Puzzle '%s' %s\(#\d+\) has been created successfully.$" % (name, name)) - lhs = [name] + parts - cmdstr = ','.join(lhs) + '=' + ','.join(results) - msg = self.call( - puzzles.CmdCreatePuzzleRecipe(), - cmdstr, - caller=self.char1 - ) - matches = self._assert_msg_matched(msg, regexs, re_flags=re.MULTILINE | re.DOTALL) - self._assert_recipe(name, parts, results) - - _good_recipe('makefire', ['stone', 'flint'], ['fire', 'stone', 'flint']) - _good_recipe('hot stones', ['stone', 'fire'], ['stone', 'fire']) - _good_recipe('hot stones', ['stone', 'fire'], ['stone', 'fire']) + self._good_recipe('makefire', ['stone', 'flint'], ['fire', 'stone', 'flint']) + self._good_recipe('hot stones', ['stone', 'fire'], ['stone', 'fire']) + self._good_recipe('furnace', ['stone', 'stone', 'fire'], ['stone', 'stone', 'fire', 'fire', 'fire', 'fire']) # bad recipes def _bad_recipe(name, parts, results, fail_regex): with self.assertRaisesRegexp(AssertionError, fail_regex): - _good_recipe(name, parts, results) + self._good_recipe(name, parts, results) self.assert_no_recipes() _bad_recipe('name', ['nothing'], ['neither'], r"Could not find 'nothing'.") @@ -1282,3 +1293,44 @@ class TestPuzzles(CommandTest): # _bad_recipe('', ['stone', 'fire'], ['stone', 'fire'], '') # FIXME: no name becomes '' #N(#N) self._assert_no_recipes() + + def test_cmd_armpuzzle(self): + recipe_dbref = self._good_recipe('makefile', ['stone', 'flint'], ['fire', 'stone', 'flint'], and_destroy_it=False) + self._arm(recipe_dbref) + + def test_cmd_use(self): + def _use(cmdstr, msg): + msg = self.call(puzzles.CmdUsePuzzleParts(), cmdstr, msg, caller=self.char1) + return msg + + _use('', 'Use what?') + _use('something', 'There is no something around.') + _use('stone', 'You have no idea how this can be used') + _use('stone flint', 'There is no stone flint around.') + _use('stone, flint', 'You have no idea how these can be used') + + recipe_dbref = self._good_recipe('makefire', ['stone', 'flint'], ['fire'] , and_destroy_it=False) + + # although there is stone and flint + # those aren't valid puzzle parts because + # the puzzle hasn't been armed + _use('stone', 'You have no idea how this can be used') + _use('stone, flint', 'You have no idea how these can be used') + self._arm(recipe_dbref) + + # there are duplicated objects now + msg = _use('stone', None) + self.assertIsNotNone(re.match(r'^Which stone. There are many.*', msg)) + msg = _use('flint', None) + self.assertIsNotNone(re.match(r'^Which flint. There are many.*', msg)) + # delete them + self.stone.delete() + self.flint.delete() + + msg = _use('stone, flint', None) + self.assertIsNotNone(re.match(r"^You are a Genius.*", msg)) + + # trying again will fail as it was resolved already + # and the parts were destroyed + _use('stone, flint', 'There is no stone around') + _use('flint, stone', 'There is no flint around') From 921b354b9e8cf92870db48c050a60f28035722ac Mon Sep 17 00:00:00 2001 From: Henddher Pedroza Date: Mon, 10 Sep 2018 20:00:08 -0500 Subject: [PATCH 038/493] @armpuzzle tests --- evennia/contrib/tests.py | 41 ++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 1d8b2f2324..c85180efee 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -1237,22 +1237,20 @@ class TestPuzzles(CommandTest): recipe_dbref = self._assert_recipe(name, parts, results, and_destroy_it) return recipe_dbref - def _arm(self, recipe_dbref): + def _arm(self, recipe_dbref, name, parts): + regexs = [ + r"^Puzzle Recipe %s\(#\d+\) '%s' found.$" % (name, name), + r"^Spawning %d parts ...$" % (len(parts)), + ] + for p in parts: + regexs.append(r'^Part %s\(#\d+\) spawned .*$' % (p)) + regexs.append(r"^Puzzle armed successfully.$") msg = self.call( puzzles.CmdArmPuzzle(), recipe_dbref, caller=self.char1 ) - print(msg) - # TODO: add regex for parts and whatnot - # similar to _good_recipe - ''' - Puzzle Recipe makefire(#2) 'makefire' found. -Spawning 2 parts ... -Part stone(#11) spawned and placed at Room(#1) -Part flint(#12) spawned and placed at Room(#1) -Puzzle armed successfully.''' - self.assertIsNotNone(re.match(r"Puzzle Recipe .* found.*Puzzle armed successfully.", msg, re.MULTILINE | re.DOTALL)) + matches = self._assert_msg_matched(msg, regexs, re_flags=re.MULTILINE | re.DOTALL) def test_cmd_puzzle(self): self._assert_no_recipes() @@ -1295,8 +1293,23 @@ Puzzle armed successfully.''' self._assert_no_recipes() def test_cmd_armpuzzle(self): - recipe_dbref = self._good_recipe('makefile', ['stone', 'flint'], ['fire', 'stone', 'flint'], and_destroy_it=False) - self._arm(recipe_dbref) + # bad arms + self.call( + puzzles.CmdArmPuzzle(), + '1', + "A puzzle recipe's #dbref must be specified", + caller=self.char1 + ) + self.call( + puzzles.CmdArmPuzzle(), + '#1', + "Invalid puzzle '#1'", + caller=self.char1 + ) + + recipe_dbref = self._good_recipe('makefire', ['stone', 'flint'], ['fire', 'stone', 'flint'], and_destroy_it=False) + # goo arm + self._arm(recipe_dbref, 'makefire', ['stone', 'flint']) def test_cmd_use(self): def _use(cmdstr, msg): @@ -1316,7 +1329,7 @@ Puzzle armed successfully.''' # the puzzle hasn't been armed _use('stone', 'You have no idea how this can be used') _use('stone, flint', 'You have no idea how these can be used') - self._arm(recipe_dbref) + self._arm(recipe_dbref, 'makefire', ['stone', 'flint']) # there are duplicated objects now msg = _use('stone', None) From 8312d246009a535d971efacdf774d9562b2fca96 Mon Sep 17 00:00:00 2001 From: Henddher Pedroza Date: Thu, 13 Sep 2018 20:00:50 -0500 Subject: [PATCH 039/493] Revamp _bad_recipe() helper function and fix @puzzle command empty name --- evennia/commands/default/muxcommand.py | 5 +++++ evennia/contrib/puzzles.py | 3 +++ evennia/contrib/tests.py | 18 ++++++++++++------ 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/evennia/commands/default/muxcommand.py b/evennia/commands/default/muxcommand.py index 5d8d4b2890..d2d2c65986 100644 --- a/evennia/commands/default/muxcommand.py +++ b/evennia/commands/default/muxcommand.py @@ -118,6 +118,11 @@ class MuxCommand(Command): lhs, rhs = [arg.strip() for arg in args.split('=', 1)] lhslist = [arg.strip() for arg in lhs.split(',')] rhslist = [arg.strip() for arg in rhs.split(',')] + # eliminate all empty-strings + # if len(lhslist) > 0: + # lhslist = list(filter(lambda i: len(i) > 0, lhslist)) + # if len(rhslist) > 0: + # rhslist = list(filter(lambda i: len(i) > 0, rhslist)) # save to object properties: self.raw = raw diff --git a/evennia/contrib/puzzles.py b/evennia/contrib/puzzles.py index 0a2a4cb447..9fd71d4ed9 100644 --- a/evennia/contrib/puzzles.py +++ b/evennia/contrib/puzzles.py @@ -167,6 +167,9 @@ class CmdCreatePuzzleRecipe(MuxCommand): return puzzle_name = self.lhslist[0] + if len(puzzle_name) == 0: + caller.msg('Invalid puzzle name %r.' % puzzle_name) + return def is_valid_obj_location(obj): valid = True diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index c85180efee..7adf8b9dcf 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -1233,8 +1233,8 @@ class TestPuzzles(CommandTest): cmdstr, caller=self.char1 ) - matches = self._assert_msg_matched(msg, regexs, re_flags=re.MULTILINE | re.DOTALL) recipe_dbref = self._assert_recipe(name, parts, results, and_destroy_it) + matches = self._assert_msg_matched(msg, regexs, re_flags=re.MULTILINE | re.DOTALL) return recipe_dbref def _arm(self, recipe_dbref, name, parts): @@ -1272,7 +1272,7 @@ class TestPuzzles(CommandTest): _bad_syntax(',nothing') _bad_syntax('name, nothing') _bad_syntax('name, nothing =') - # _bad_syntax(', = ,') # FIXME: got: Could not find ''. + # _bad_syntax(', = ,') # FIXME: MuxCommand issue? self._assert_no_recipes() @@ -1282,13 +1282,19 @@ class TestPuzzles(CommandTest): # bad recipes def _bad_recipe(name, parts, results, fail_regex): - with self.assertRaisesRegexp(AssertionError, fail_regex): - self._good_recipe(name, parts, results) - self.assert_no_recipes() + cmdstr = ','.join([name] + parts) \ + + '=' + ','.join(results) + msg = self.call( + puzzles.CmdCreatePuzzleRecipe(), + cmdstr, + caller=self.char1 + ) + self._assert_no_recipes() + self.assertIsNotNone(re.match(fail_regex, msg), msg) _bad_recipe('name', ['nothing'], ['neither'], r"Could not find 'nothing'.") _bad_recipe('name', ['stone'], ['nothing'], r"Could not find 'nothing'.") - # _bad_recipe('', ['stone', 'fire'], ['stone', 'fire'], '') # FIXME: no name becomes '' #N(#N) + _bad_recipe('', ['stone', 'fire'], ['stone', 'fire'], r"^Invalid puzzle name ''.") self._assert_no_recipes() From e51ff36801ab746e6561bd6a8cee84add460134e Mon Sep 17 00:00:00 2001 From: Henddher Pedroza Date: Fri, 14 Sep 2018 21:57:28 -0500 Subject: [PATCH 040/493] Minor cleanup --- evennia/commands/default/muxcommand.py | 5 ----- evennia/contrib/puzzles.py | 5 ----- evennia/contrib/tests.py | 1 - 3 files changed, 11 deletions(-) diff --git a/evennia/commands/default/muxcommand.py b/evennia/commands/default/muxcommand.py index d2d2c65986..5d8d4b2890 100644 --- a/evennia/commands/default/muxcommand.py +++ b/evennia/commands/default/muxcommand.py @@ -118,11 +118,6 @@ class MuxCommand(Command): lhs, rhs = [arg.strip() for arg in args.split('=', 1)] lhslist = [arg.strip() for arg in lhs.split(',')] rhslist = [arg.strip() for arg in rhs.split(',')] - # eliminate all empty-strings - # if len(lhslist) > 0: - # lhslist = list(filter(lambda i: len(i) > 0, lhslist)) - # if len(rhslist) > 0: - # rhslist = list(filter(lambda i: len(i) > 0, rhslist)) # save to object properties: self.raw = raw diff --git a/evennia/contrib/puzzles.py b/evennia/contrib/puzzles.py index 9fd71d4ed9..e0423105f5 100644 --- a/evennia/contrib/puzzles.py +++ b/evennia/contrib/puzzles.py @@ -99,7 +99,6 @@ def proto_def(obj, with_tags=True): 'typeclass': 'evennia.contrib.puzzles.PuzzlePartObject', # FIXME: what if obj is another typeclass 'desc': obj.db.desc, 'location': obj.location, - # FIXME: Can tags be INVISIBLE? We don't want player to know an object belongs to a puzzle 'tags': [(_PUZZLES_TAG_MEMBER, _PUZZLES_TAG_CATEGORY)], } if not with_tags: @@ -235,10 +234,6 @@ class CmdCreatePuzzleRecipe(MuxCommand): 'Or programmatically.\n' ) - # FIXME: puzzle recipe object exists but it has no location - # should we create a PuzzleLibrary where all puzzles are - # kept and cannot be reached by players? - class CmdArmPuzzle(MuxCommand): """ diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 7adf8b9dcf..ab206dc632 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -1272,7 +1272,6 @@ class TestPuzzles(CommandTest): _bad_syntax(',nothing') _bad_syntax('name, nothing') _bad_syntax('name, nothing =') - # _bad_syntax(', = ,') # FIXME: MuxCommand issue? self._assert_no_recipes() From 2e376a32cbb3d178621f8c752ee377efe04c38fc Mon Sep 17 00:00:00 2001 From: Henddher Pedroza Date: Fri, 14 Sep 2018 23:28:56 -0500 Subject: [PATCH 041/493] Enforce parts and results to be DefaultObject not DefaultCharacter, DefaultRoom nor DefaultExit with tests. Tests for @lspuzzlerecipes and @lsarmedpuzzles --- evennia/contrib/puzzles.py | 21 +++++++++- evennia/contrib/tests.py | 81 +++++++++++++++++++++++++++++++++++++- 2 files changed, 99 insertions(+), 3 deletions(-) diff --git a/evennia/contrib/puzzles.py b/evennia/contrib/puzzles.py index e0423105f5..cf89a4834c 100644 --- a/evennia/contrib/puzzles.py +++ b/evennia/contrib/puzzles.py @@ -75,6 +75,7 @@ from evennia import DefaultObject from evennia import DefaultScript from evennia import DefaultCharacter from evennia import DefaultRoom +from evennia import DefaultExit from evennia.commands.default.muxcommand import MuxCommand from evennia.utils.utils import inherits_from from evennia.utils import search, utils, logger @@ -193,12 +194,28 @@ class CmdCreatePuzzleRecipe(MuxCommand): def is_valid_result_location(part): return is_valid_obj_location(part) + def is_valid_inheritance(obj): + valid = not inherits_from(obj, DefaultCharacter) \ + and not inherits_from(obj, DefaultRoom) \ + and not inherits_from(obj, DefaultExit) + if not valid: + caller.msg('Invalid typeclass for %s' % (obj)) + return valid + + def is_valid_part(part): + return is_valid_inheritance(part) \ + and is_valid_part_location(part) + + def is_valid_result(result): + return is_valid_inheritance(result) \ + and is_valid_result_location(result) + parts = [] for objname in self.lhslist[1:]: obj = caller.search(objname) if not obj: return - if not is_valid_part_location(obj): + if not is_valid_part(obj): return parts.append(obj) @@ -207,7 +224,7 @@ class CmdCreatePuzzleRecipe(MuxCommand): obj = caller.search(objname) if not obj: return - if not is_valid_result_location(obj): + if not is_valid_result(obj): return results.append(obj) diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index ab206dc632..0e61a66f33 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -1215,7 +1215,7 @@ class TestPuzzles(CommandTest): def _assert_no_recipes(self): self.assertEqual( 0, - len(search.search_tag('', category=puzzles._PUZZLES_TAG_CATEGORY)) + len(search.search_script_tag('', category=puzzles._PUZZLES_TAG_CATEGORY)) ) # good recipes @@ -1294,6 +1294,11 @@ class TestPuzzles(CommandTest): _bad_recipe('name', ['nothing'], ['neither'], r"Could not find 'nothing'.") _bad_recipe('name', ['stone'], ['nothing'], r"Could not find 'nothing'.") _bad_recipe('', ['stone', 'fire'], ['stone', 'fire'], r"^Invalid puzzle name ''.") + self.stone.location = self.char1 + _bad_recipe('name', ['stone'], ['fire'], r"^Invalid location for stone$") + _bad_recipe('name', ['flint'], ['stone'], r"^Invalid location for stone$") + _bad_recipe('name', ['self'], ['fire'], r"^Invalid typeclass for Char$") + _bad_recipe('name', ['here'], ['fire'], r"^Invalid typeclass for Room$") self._assert_no_recipes() @@ -1352,3 +1357,77 @@ class TestPuzzles(CommandTest): # and the parts were destroyed _use('stone, flint', 'There is no stone around') _use('flint, stone', 'There is no flint around') + + def test_lspuzzlerecipes_lsarmedpuzzles(self): + msg = self.call( + puzzles.CmdListPuzzleRecipes(), + '', + caller=self.char1 + ) + self._assert_msg_matched( + msg, + [ + r"^-+$", + r"^-+$", + ], + re.MULTILINE | re.DOTALL + ) + + recipe_dbref = self._good_recipe( + 'makefire', ['stone', 'flint'], ['fire'], + and_destroy_it=False) + + msg = self.call( + puzzles.CmdListPuzzleRecipes(), + '', + caller=self.char1 + ) + self._assert_msg_matched( + msg, + [ + r"^-+$", + r"^Puzzle 'makefire'.*$", + r"^Parts$", + r"^.*key: stone$", + r"^.*key: flint$", + r"^Results$", + r"^.*key: fire$", + r"^.*key: stone$", + r"^.*key: flint$", + r"^-+$", + ], + re.MULTILINE | re.DOTALL + ) + + msg = self.call( + puzzles.CmdListArmedPuzzles(), + '', + caller=self.char1 + ) + self._assert_msg_matched( + msg, + [ + r"^-+$", + r"^-+$" + ], + re.MULTILINE | re.DOTALL + ) + + self._arm(recipe_dbref, 'makefire', ['stone', 'flint']) + + msg = self.call( + puzzles.CmdListArmedPuzzles(), + '', + caller=self.char1 + ) + self._assert_msg_matched( + msg, + [ + r"^-+$", + r"^Puzzle name: makefire$", + r"^.*stone.* at \s+ Room.*$", + r"^.*flint.* at \s+ Room.*$", + r"^-+$", + ], + re.MULTILINE | re.DOTALL + ) From bbae3cea4728566d5178e6f568c7ccdbcb264a2c Mon Sep 17 00:00:00 2001 From: Henddher Pedroza Date: Sat, 15 Sep 2018 01:36:33 -0500 Subject: [PATCH 042/493] Increase test coverage for puzzles module --- evennia/contrib/tests.py | 93 +++++++++++++++++++++++++++++++--------- 1 file changed, 73 insertions(+), 20 deletions(-) diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 0e61a66f33..6841f92adb 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -1174,8 +1174,10 @@ class TestRandomStringGenerator(EvenniaTest): # Test of the Puzzles module +import itertools from evennia.contrib import puzzles from evennia.utils import search +from evennia.utils.utils import inherits_from class TestPuzzles(CommandTest): @@ -1193,23 +1195,23 @@ class TestPuzzles(CommandTest): matches.append(m) return matches - def _assert_recipe(self, name, parts, results, and_destroy_it=True): + def _assert_recipe(self, name, parts, results, and_destroy_it=True, expected_count=1): def _keys(items): return [item['key'] for item in items] recipes = search.search_script_tag('', category=puzzles._PUZZLES_TAG_CATEGORY) - self.assertEqual(1, len(recipes)) - self.assertEqual(name, recipes[0].db.puzzle_name) - self.assertEqual(parts, _keys(recipes[0].db.parts)) - self.assertEqual(results, _keys(recipes[0].db.results)) + self.assertEqual(expected_count, len(recipes)) + self.assertEqual(name, recipes[expected_count-1].db.puzzle_name) + self.assertEqual(parts, _keys(recipes[expected_count-1].db.parts)) + self.assertEqual(results, _keys(recipes[expected_count-1].db.results)) self.assertEqual( puzzles._PUZZLES_TAG_RECIPE, - recipes[0].tags.get(category=puzzles._PUZZLES_TAG_CATEGORY) + recipes[expected_count-1].tags.get(category=puzzles._PUZZLES_TAG_CATEGORY) ) - recipe_dbref = recipes[0].dbref + recipe_dbref = recipes[expected_count-1].dbref if and_destroy_it: - recipes[0].delete() + recipes[expected_count-1].delete() return recipe_dbref if not and_destroy_it else None def _assert_no_recipes(self): @@ -1219,7 +1221,7 @@ class TestPuzzles(CommandTest): ) # good recipes - def _good_recipe(self, name, parts, results, and_destroy_it=True): + def _good_recipe(self, name, parts, results, and_destroy_it=True, expected_count=1): regexs = [] for p in parts: regexs.append(r'^Part %s\(#\d+\)$' % (p)) @@ -1233,10 +1235,19 @@ class TestPuzzles(CommandTest): cmdstr, caller=self.char1 ) - recipe_dbref = self._assert_recipe(name, parts, results, and_destroy_it) + recipe_dbref = self._assert_recipe(name, parts, results, and_destroy_it, expected_count) matches = self._assert_msg_matched(msg, regexs, re_flags=re.MULTILINE | re.DOTALL) return recipe_dbref + def _check_room_contents(self, expected): + by_obj_key = lambda o: o.key + room1_contents = sorted(self.room1.contents, key=by_obj_key) + for key, grp in itertools.groupby(room1_contents, by_obj_key): + if key in expected: + grp = list(grp) + self.assertEqual(expected[key], len(grp), + "Expected %d but got %d for %s" % (expected[key], len(grp), key)) + def _arm(self, recipe_dbref, name, parts): regexs = [ r"^Puzzle Recipe %s\(#\d+\) '%s' found.$" % (name, name), @@ -1318,12 +1329,19 @@ class TestPuzzles(CommandTest): ) recipe_dbref = self._good_recipe('makefire', ['stone', 'flint'], ['fire', 'stone', 'flint'], and_destroy_it=False) - # goo arm + + # delete proto parts and proto result + self.stone.delete() + self.flint.delete() + self.fire.delete() + + # good arm self._arm(recipe_dbref, 'makefire', ['stone', 'flint']) + self._check_room_contents({'stone': 1, 'flint': 1}) def test_cmd_use(self): - def _use(cmdstr, msg): - msg = self.call(puzzles.CmdUsePuzzleParts(), cmdstr, msg, caller=self.char1) + def _use(cmdstr, expmsg): + msg = self.call(puzzles.CmdUsePuzzleParts(), cmdstr, expmsg, caller=self.char1) return msg _use('', 'Use what?') @@ -1333,6 +1351,8 @@ class TestPuzzles(CommandTest): _use('stone, flint', 'You have no idea how these can be used') recipe_dbref = self._good_recipe('makefire', ['stone', 'flint'], ['fire'] , and_destroy_it=False) + recipe2_dbref = self._good_recipe('makefire2', ['stone', 'flint'], ['fire'] , and_destroy_it=False, + expected_count=2) # although there is stone and flint # those aren't valid puzzle parts because @@ -1340,24 +1360,57 @@ class TestPuzzles(CommandTest): _use('stone', 'You have no idea how this can be used') _use('stone, flint', 'You have no idea how these can be used') self._arm(recipe_dbref, 'makefire', ['stone', 'flint']) + self._check_room_contents({'stone': 2, 'flint': 2}) # there are duplicated objects now - msg = _use('stone', None) - self.assertIsNotNone(re.match(r'^Which stone. There are many.*', msg)) - msg = _use('flint', None) - self.assertIsNotNone(re.match(r'^Which flint. There are many.*', msg)) - # delete them + _use('stone', 'Which stone. There are many') + _use('flint', 'Which flint. There are many') + + # delete proto parts self.stone.delete() self.flint.delete() + # delete proto result + self.fire.delete() - msg = _use('stone, flint', None) - self.assertIsNotNone(re.match(r"^You are a Genius.*", msg)) + # solve puzzle + _use('stone, flint', 'You are a Genius') + self.assertEqual(1, + len(list(filter( + lambda o: o.key == 'fire' \ + and inherits_from(o,'evennia.contrib.puzzles.PuzzlePartObject'), + self.room1.contents)))) + self._check_room_contents({'stone': 0, 'flint': 0, 'fire': 1}) # trying again will fail as it was resolved already # and the parts were destroyed _use('stone, flint', 'There is no stone around') _use('flint, stone', 'There is no flint around') + # arm same puzzle twice so there are duplicated parts + self._arm(recipe_dbref, 'makefire', ['stone', 'flint']) + self._arm(recipe_dbref, 'makefire', ['stone', 'flint']) + self._check_room_contents({'stone': 2, 'flint': 2, 'fire': 1}) + + # try solving with multiple parts but incomplete set + _use('1-stone, 2-stone', 'As you try to utilize these, nothing happens.') + + # arm the other puzzle. Their parts are identical + self._arm(recipe2_dbref, 'makefire2', ['stone', 'flint']) + self._check_room_contents({'stone': 3, 'flint': 3, 'fire': 1}) + + # solve with multiple parts for + # multiple puzzles. Both can be solved but + # only one is. + _use( + '1-stone, 2-flint, 3-stone, 3-flint', + 'Your gears start turning and a bunch of ideas come to your mind ... ') + self._check_room_contents({'stone': 2, 'flint': 2, 'fire': 2}) + + # solve all + _use('1-stone, 1-flint', 'You are a Genius') + _use('stone, flint', 'You are a Genius') + self._check_room_contents({'stone': 0, 'flint': 0, 'fire': 4}) + def test_lspuzzlerecipes_lsarmedpuzzles(self): msg = self.call( puzzles.CmdListPuzzleRecipes(), From 1de840514b549b68b73433df234302e0815118d4 Mon Sep 17 00:00:00 2001 From: Henddher Pedroza Date: Sat, 15 Sep 2018 12:02:22 -0500 Subject: [PATCH 043/493] E2E tests for puzzles --- evennia/contrib/tests.py | 126 ++++++++++++++++++++++++++++++++------- 1 file changed, 106 insertions(+), 20 deletions(-) diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 6841f92adb..a9396cc78e 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -1263,6 +1263,9 @@ class TestPuzzles(CommandTest): ) matches = self._assert_msg_matched(msg, regexs, re_flags=re.MULTILINE | re.DOTALL) + def test_cmdset_puzzle(self): + self.char1.cmdset.add('evennia.contrib.puzzles.PuzzleSystemCmdSet') + def test_cmd_puzzle(self): self._assert_no_recipes() @@ -1339,16 +1342,22 @@ class TestPuzzles(CommandTest): self._arm(recipe_dbref, 'makefire', ['stone', 'flint']) self._check_room_contents({'stone': 1, 'flint': 1}) - def test_cmd_use(self): - def _use(cmdstr, expmsg): - msg = self.call(puzzles.CmdUsePuzzleParts(), cmdstr, expmsg, caller=self.char1) - return msg + def _use(self, cmdstr, expmsg): + msg = self.call( + puzzles.CmdUsePuzzleParts(), + cmdstr, + expmsg, + caller=self.char1 + ) + return msg - _use('', 'Use what?') - _use('something', 'There is no something around.') - _use('stone', 'You have no idea how this can be used') - _use('stone flint', 'There is no stone flint around.') - _use('stone, flint', 'You have no idea how these can be used') + def test_cmd_use(self): + + self._use('', 'Use what?') + self._use('something', 'There is no something around.') + self._use('stone', 'You have no idea how this can be used') + self._use('stone flint', 'There is no stone flint around.') + self._use('stone, flint', 'You have no idea how these can be used') recipe_dbref = self._good_recipe('makefire', ['stone', 'flint'], ['fire'] , and_destroy_it=False) recipe2_dbref = self._good_recipe('makefire2', ['stone', 'flint'], ['fire'] , and_destroy_it=False, @@ -1357,14 +1366,14 @@ class TestPuzzles(CommandTest): # although there is stone and flint # those aren't valid puzzle parts because # the puzzle hasn't been armed - _use('stone', 'You have no idea how this can be used') - _use('stone, flint', 'You have no idea how these can be used') + self._use('stone', 'You have no idea how this can be used') + self._use('stone, flint', 'You have no idea how these can be used') self._arm(recipe_dbref, 'makefire', ['stone', 'flint']) self._check_room_contents({'stone': 2, 'flint': 2}) # there are duplicated objects now - _use('stone', 'Which stone. There are many') - _use('flint', 'Which flint. There are many') + self._use('stone', 'Which stone. There are many') + self._use('flint', 'Which flint. There are many') # delete proto parts self.stone.delete() @@ -1373,7 +1382,7 @@ class TestPuzzles(CommandTest): self.fire.delete() # solve puzzle - _use('stone, flint', 'You are a Genius') + self._use('stone, flint', 'You are a Genius') self.assertEqual(1, len(list(filter( lambda o: o.key == 'fire' \ @@ -1383,8 +1392,8 @@ class TestPuzzles(CommandTest): # trying again will fail as it was resolved already # and the parts were destroyed - _use('stone, flint', 'There is no stone around') - _use('flint, stone', 'There is no flint around') + self._use('stone, flint', 'There is no stone around') + self._use('flint, stone', 'There is no flint around') # arm same puzzle twice so there are duplicated parts self._arm(recipe_dbref, 'makefire', ['stone', 'flint']) @@ -1392,7 +1401,7 @@ class TestPuzzles(CommandTest): self._check_room_contents({'stone': 2, 'flint': 2, 'fire': 1}) # try solving with multiple parts but incomplete set - _use('1-stone, 2-stone', 'As you try to utilize these, nothing happens.') + self._use('1-stone, 2-stone', 'As you try to utilize these, nothing happens.') # arm the other puzzle. Their parts are identical self._arm(recipe2_dbref, 'makefire2', ['stone', 'flint']) @@ -1401,14 +1410,14 @@ class TestPuzzles(CommandTest): # solve with multiple parts for # multiple puzzles. Both can be solved but # only one is. - _use( + self._use( '1-stone, 2-flint, 3-stone, 3-flint', 'Your gears start turning and a bunch of ideas come to your mind ... ') self._check_room_contents({'stone': 2, 'flint': 2, 'fire': 2}) # solve all - _use('1-stone, 1-flint', 'You are a Genius') - _use('stone, flint', 'You are a Genius') + self._use('1-stone, 1-flint', 'You are a Genius') + self._use('stone, flint', 'You are a Genius') self._check_room_contents({'stone': 0, 'flint': 0, 'fire': 4}) def test_lspuzzlerecipes_lsarmedpuzzles(self): @@ -1484,3 +1493,80 @@ class TestPuzzles(CommandTest): ], re.MULTILINE | re.DOTALL ) + + def test_e2e(self): + + def _destroy_objs_in_room(keys): + for obj in self.room1.contents: + if obj.key in keys: + obj.delete() + + # parts don't survive resolution + # but produce a large result set + tree = create_object(key='tree', location=self.char1.location) + axe = create_object(key='axe', location=self.char1.location) + sweat = create_object(key='sweat', location=self.char1.location) + dull_axe = create_object(key='dull axe', location=self.char1.location) + timber = create_object(key='timber', location=self.char1.location) + log = create_object(key='log', location=self.char1.location) + parts = ['tree', 'axe'] + results = (['sweat'] * 10) + ['dull axe'] + (['timber'] * 20) + (['log'] * 50) + recipe_dbref = self._good_recipe( + 'lumberjack', + parts, results, + and_destroy_it=False + ) + + _destroy_objs_in_room(set(parts + results)) + + sps = sorted(parts) + expected = {key: len(list(grp)) for key, grp in itertools.groupby(sps)} + expected.update({r: 0 for r in set(results)}) + + self._arm(recipe_dbref, 'lumberjack', parts) + self._check_room_contents(expected) + + self._use(','.join(parts), 'You are a Genius') + srs = sorted(set(results)) + expected = {(key, len(list(grp))) for key, grp in itertools.groupby(srs)} + expected.update({p: 0 for p in set(parts)}) + self._check_room_contents(expected) + + # parts also appear in results + # causing a new puzzle to be armed 'automatically' + # i.e. the puzzle is self-sustaining + hole = create_object(key='hole', location=self.char1.location) + shovel = create_object(key='shovel', location=self.char1.location) + dirt = create_object(key='dirt', location=self.char1.location) + + parts = ['shovel', 'hole'] + results = ['dirt', 'hole', 'shovel'] + recipe_dbref = self._good_recipe( + 'digger', + parts, results, + and_destroy_it=False, + expected_count=2 + ) + + _destroy_objs_in_room(set(parts + results)) + + nresolutions = 0 + + sps = sorted(set(parts)) + expected = {key: len(list(grp)) for key, grp in itertools.groupby(sps)} + expected.update({'dirt': nresolutions}) + + self._arm(recipe_dbref, 'digger', parts) + self._check_room_contents(expected) + + for i in range(10): + self._use(','.join(parts), 'You are a Genius') + nresolutions += 1 + expected.update({'dirt': nresolutions}) + self._check_room_contents(expected) + + # TODO: results has Exit + + # TODO: results has NPC + + # TODO: results has Room From 1c87107b9d37a065fdb07d00d4d1814e910fd729 Mon Sep 17 00:00:00 2001 From: Henddher Pedroza Date: Sat, 15 Sep 2018 21:08:48 -0500 Subject: [PATCH 044/493] Add @puzzleedit and puzzle.db.use_success_message so puzzle resolution message can be customized by builder --- evennia/contrib/puzzles.py | 101 ++++++++++++++++++++++++++++++++----- evennia/contrib/tests.py | 45 +++++++++++++++-- 2 files changed, 131 insertions(+), 15 deletions(-) diff --git a/evennia/contrib/puzzles.py b/evennia/contrib/puzzles.py index cf89a4834c..4f95062c5e 100644 --- a/evennia/contrib/puzzles.py +++ b/evennia/contrib/puzzles.py @@ -87,6 +87,9 @@ _PUZZLES_TAG_RECIPE = 'puzzle_recipe' # puzzle part and puzzle result _PUZZLES_TAG_MEMBER = 'puzzle_member' +_PUZZLE_DEFAULT_FAIL_USE_MESSAGE = 'You try to utilize %s but nothing happens ... something amiss?' +_PUZZLE_DEFAULT_SUCCESS_USE_MESSAGE = 'You are a Genius!!!' + # ----------- UTILITY FUNCTIONS ------------ def proto_def(obj, with_tags=True): @@ -106,6 +109,15 @@ def proto_def(obj, with_tags=True): del(protodef['tags']) return protodef +# Colorize the default success message +_i = 0 +_colors = ['|r', '|g', '|y'] +_msg = [] +for l in _PUZZLE_DEFAULT_SUCCESS_USE_MESSAGE: + _msg += _colors[_i] + l + _i = (_i + 1) % len(_colors) +_PUZZLE_DEFAULT_SUCCESS_USE_MESSAGE = ''.join(_msg) + '|n' + # ------------------------------------------ class PuzzlePartObject(DefaultObject): @@ -138,6 +150,7 @@ class PuzzleRecipe(DefaultScript): self.db.parts = tuple(parts) self.db.results = tuple(results) self.tags.add(_PUZZLES_TAG_RECIPE, category=_PUZZLES_TAG_CATEGORY) + self.db.use_success_message = _PUZZLE_DEFAULT_SUCCESS_USE_MESSAGE class CmdCreatePuzzleRecipe(MuxCommand): @@ -246,12 +259,80 @@ class CmdCreatePuzzleRecipe(MuxCommand): caller.msg( 'You may now dispose all parts and results. ' 'Typically, results and parts are useless afterwards.\n' + 'Remember to add a "success message" via:\n' + ' @puzzleedit #dbref/use_success_message = \n' 'You are now able to arm this puzzle using Builder command:\n' - ' @armpuzzle \n\n' - 'Or programmatically.\n' + ' @armpuzzle \n' ) +class CmdEditPuzzle(MuxCommand): + """ + Edits puzzle properties + + Usage: + @puzzleedit[/delete] <#dbref> + @puzzleedit <#dbref>/use_success_message = + + Switches: + delete - deletes the recipe. Existing parts and results aren't modified + + """ + + key = '@puzzleedit' + # FIXME: permissions for scripts? + locks = 'cmd:perm(puzzleedit) or perm(Builder)' + help_category = 'Puzzles' + + def func(self): + _USAGE = "Usage: @puzzleedit[/switches] [/attribute = ]" + caller = self.caller + + if not self.lhslist: + caller.msg(_USAGE) + return + + if '/' in self.lhslist[0]: + recipe_dbref, attr = self.lhslist[0].split('/') + else: + recipe_dbref = self.lhslist[0] + + if not utils.dbref(recipe_dbref): + caller.msg("A puzzle recipe's #dbref must be specified.\n" + _USAGE) + return + + puzzle = search.search_script(recipe_dbref) + if not puzzle or not inherits_from(puzzle[0], PuzzleRecipe): + caller.msg('Invalid puzzle %r' % (recipe_dbref)) + return + + puzzle = puzzle[0] + puzzle_name_id = '%s(%s)' % (puzzle.name, puzzle.dbref) + + if 'delete' in self.switches: + if not (puzzle.access(caller, 'control') or puzzle.access(caller, 'delete')): + caller.msg("You don't have permission to delete %s." % puzzle_name_id) + return + + puzzle.delete() + caller.msg('%s was deleted' % puzzle_name_id) + return + + else: + # edit attributes + + if not (puzzle.access(caller, 'control') or puzzle.access(caller, 'edit')): + caller.msg("You don't have permission to edit %s." % puzzle_name_id) + return + + if attr == 'use_success_message': + puzzle.db.use_success_message = self.rhs + caller.msg( + "%s use_success_message = %s\n" % (puzzle_name_id, puzzle.db.use_success_message) + ) + return + + class CmdArmPuzzle(MuxCommand): """ Arms a puzzle by spawning all its parts @@ -396,10 +477,10 @@ class CmdUsePuzzleParts(MuxCommand): matched_puzzles[puzzle.dbref] = matched_dbrefparts if len(matched_puzzles) == 0: - # FIXME: Add more random messages + # TODO: we could use part.fail_message instead, if any # random part falls and lands on your feet # random part hits you square on the face - caller.msg("As you try to utilize %s, nothing happens." % (many)) + caller.msg(_PUZZLE_DEFAULT_FAIL_USE_MESSAGE % (many)) return puzzletuples = sorted(matched_puzzles.items(), key=lambda t: len(t[1]), reverse=True) @@ -440,17 +521,11 @@ class CmdUsePuzzleParts(MuxCommand): for dbref in matched_dbrefparts: parts_dict[dbref].delete() - # FIXME: Add random messages - # You are a genius ... no matter what your 2nd grade teacher told you - # You hear thunders and a cloud of dust raises leaving result_names = ', '.join(result_names) - caller.msg( - "You are a |wG|re|wn|ri|wu|rs|n!!!\nYou just created %s" % ( - result_names - )) + caller.msg(puzzle.db.use_success_message) caller.location.msg_contents( "|c%s|n performs some kind of tribal dance" - " and seems to create |y%s|n from thin air" % ( + " and |y%s|n seems to appear from thin air" % ( caller, result_names), exclude=(caller,) ) @@ -479,6 +554,7 @@ class CmdListPuzzleRecipes(MuxCommand): msgf_item = "%2s|c%15s|n: |w%s|n" for recipe in recipes: text.append(msgf_recipe % (recipe.db.puzzle_name, recipe.name, recipe.dbref)) + text.append('Success message: ' + recipe.db.use_success_message) text.append('Parts') for protopart in recipe.db.parts[:]: mark = '-' @@ -542,6 +618,7 @@ class PuzzleSystemCmdSet(CmdSet): super(PuzzleSystemCmdSet, self).at_cmdset_creation() self.add(CmdCreatePuzzleRecipe()) + self.add(CmdEditPuzzle()) self.add(CmdArmPuzzle()) self.add(CmdListPuzzleRecipes()) self.add(CmdListArmedPuzzles()) diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index a9396cc78e..efd2a3cdb0 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -1265,6 +1265,7 @@ class TestPuzzles(CommandTest): def test_cmdset_puzzle(self): self.char1.cmdset.add('evennia.contrib.puzzles.PuzzleSystemCmdSet') + # FIXME: testing nothing, this is just to bump up coverage def test_cmd_puzzle(self): self._assert_no_recipes() @@ -1375,10 +1376,9 @@ class TestPuzzles(CommandTest): self._use('stone', 'Which stone. There are many') self._use('flint', 'Which flint. There are many') - # delete proto parts + # delete proto parts and proto results self.stone.delete() self.flint.delete() - # delete proto result self.fire.delete() # solve puzzle @@ -1401,7 +1401,7 @@ class TestPuzzles(CommandTest): self._check_room_contents({'stone': 2, 'flint': 2, 'fire': 1}) # try solving with multiple parts but incomplete set - self._use('1-stone, 2-stone', 'As you try to utilize these, nothing happens.') + self._use('1-stone, 2-stone', 'You try to utilize these but nothing happens ... something amiss?') # arm the other puzzle. Their parts are identical self._arm(recipe2_dbref, 'makefire2', ['stone', 'flint']) @@ -1420,6 +1420,44 @@ class TestPuzzles(CommandTest): self._use('stone, flint', 'You are a Genius') self._check_room_contents({'stone': 0, 'flint': 0, 'fire': 4}) + def test_puzzleedit(self): + recipe_dbref = self._good_recipe('makefire', ['stone', 'flint'], ['fire'] , and_destroy_it=False) + + # delete proto parts and proto results + self.stone.delete() + self.flint.delete() + self.fire.delete() + + def _puzzleedit(swt, dbref, args, expmsg): + self.call( + puzzles.CmdEditPuzzle(), + '%s %s%s' % (swt, dbref, args), + expmsg, + caller=self.char1 + ) + + # bad syntax + _puzzleedit('', '1', '', "A puzzle recipe's #dbref must be specified.\nUsage: @puzzleedit") + _puzzleedit('', '', '', "A puzzle recipe's #dbref must be specified.\nUsage: @puzzleedit") + _puzzleedit('', recipe_dbref, 'dummy', "A puzzle recipe's #dbref must be specified.\nUsage: @puzzleedit") + + # no permissions + _puzzleedit('', recipe_dbref, '/use_success_message = Yes!', "You don't have permission") + _puzzleedit('/delete', recipe_dbref, '', "You don't have permission") + + # grant perm to char1 + puzzle = search.search_script(recipe_dbref)[0] + puzzle.locks.add('control:id(%s)' % self.char1.dbref[1:]) + + # edit use_success_message + _puzzleedit('', recipe_dbref, '/use_success_message = Yes!', 'makefire(%s) use_success_message = Yes!' % recipe_dbref) + self._arm(recipe_dbref, 'makefire', ['stone', 'flint']) + self._use('stone, flint', 'Yes!') + + # delete + _puzzleedit('/delete', recipe_dbref, '', 'makefire(%s) was deleted' % recipe_dbref) + self._assert_no_recipes() + def test_lspuzzlerecipes_lsarmedpuzzles(self): msg = self.call( puzzles.CmdListPuzzleRecipes(), @@ -1449,6 +1487,7 @@ class TestPuzzles(CommandTest): [ r"^-+$", r"^Puzzle 'makefire'.*$", + r"^Success message: .*$", r"^Parts$", r"^.*key: stone$", r"^.*key: flint$", From b466177fc696e3c4a9f75b210ba28043fc40ad0e Mon Sep 17 00:00:00 2001 From: Henddher Pedroza Date: Sun, 16 Sep 2018 17:25:57 -0500 Subject: [PATCH 045/493] Remove one-to-one part/result to puzzle relationship based on puzzle_name. Instead, Tags puzzle_name:_PUZZLES_TAG_CATEGORY are used for matching. This allows to use older PuzzlePartObjects in newly created puzzles by adding the new puzzles' puzzle_name:_PUZZLES_TAG_CATEGORY tag to the old objects. When creating proto parts and results, honor obj.home, obj.permissions, and obj.locks, and obj.tags --- evennia/contrib/puzzles.py | 90 ++++++++++++++++---------------- evennia/contrib/tests.py | 104 ++++++++++++++++++++++++++++++++++++- 2 files changed, 147 insertions(+), 47 deletions(-) diff --git a/evennia/contrib/puzzles.py b/evennia/contrib/puzzles.py index 4f95062c5e..19a291358d 100644 --- a/evennia/contrib/puzzles.py +++ b/evennia/contrib/puzzles.py @@ -98,15 +98,19 @@ def proto_def(obj, with_tags=True): and compare recipe with candidate part """ protodef = { - # FIXME: Don't we need to honor ALL properties? locks, perms, etc. + # FIXME: Don't we need to honor ALL properties? attributes, contents, etc. 'key': obj.key, 'typeclass': 'evennia.contrib.puzzles.PuzzlePartObject', # FIXME: what if obj is another typeclass 'desc': obj.db.desc, 'location': obj.location, - 'tags': [(_PUZZLES_TAG_MEMBER, _PUZZLES_TAG_CATEGORY)], + 'home': obj.home, + 'locks': ';'.join(obj.locks.all()), + 'permissions': obj.permissions.all()[:], } - if not with_tags: - del(protodef['tags']) + if with_tags: + tags = obj.tags.all()[:] + tags.append((_PUZZLES_TAG_MEMBER, _PUZZLES_TAG_CATEGORY)) + protodef['tags'] = tags return protodef # Colorize the default success message @@ -128,16 +132,10 @@ class PuzzlePartObject(DefaultObject): def mark_as_puzzle_member(self, puzzle_name): """ Marks this object as a member of puzzle named - puzzle_name + 'puzzle_name' """ - # FIXME: if multiple puzzles have the same - # puzzle_name, their ingredients may be - # combined but leave other parts orphan - # Similarly, if a puzzle_name were changed, - # its parts will become orphan - # Perhaps we should use #dbref but that will - # force specific parts to be combined self.db.puzzle_name = puzzle_name + self.tags.add(puzzle_name, category=_PUZZLES_TAG_CATEGORY) class PuzzleRecipe(DefaultScript): @@ -184,6 +182,10 @@ class CmdCreatePuzzleRecipe(MuxCommand): caller.msg('Invalid puzzle name %r.' % puzzle_name) return + # TODO: if there is another puzzle with same name + # warn user that parts and results will be + # interchangable + def is_valid_obj_location(obj): valid = True # Valid locations are: room, ... @@ -422,24 +424,24 @@ class CmdUsePuzzleParts(MuxCommand): # a valid part parts.append(part) - # Create lookup dict - parts_dict = dict((part.dbref, part) for part in parts) - - # Group parts by their puzzle name + # Create lookup dicts by part's dbref and by puzzle_name(tags) + parts_dict = dict() + puzzlename_tags_dict = dict() puzzle_ingredients = dict() for part in parts: - puzzle_name = part.db.puzzle_name - if puzzle_name not in puzzle_ingredients: - puzzle_ingredients[puzzle_name] = [] - puzzle_ingredients[puzzle_name].append( - (part.dbref, proto_def(part, with_tags=False)) - ) + parts_dict[part.dbref] = part + puzzle_ingredients[part.dbref] = proto_def(part, with_tags=False) + tags_categories = part.tags.all(return_key_and_category=True) + for tag, category in tags_categories: + if category != _PUZZLES_TAG_CATEGORY: + continue + if tag not in puzzlename_tags_dict: + puzzlename_tags_dict[tag] = [] + puzzlename_tags_dict[tag].append(part.dbref) - - # Find all puzzles by puzzle name - # FIXME: we rely on obj.db.puzzle_name which is visible and may be cnaged afterwards. Can we lock it and hide it? + # Find all puzzles by puzzle name (i.e. tag name) puzzles = [] - for puzzle_name, parts in puzzle_ingredients.items(): + for puzzle_name, parts in puzzlename_tags_dict.items(): _puzzles = search.search_script_attribute( key='puzzle_name', value=puzzle_name @@ -450,30 +452,26 @@ class CmdUsePuzzleParts(MuxCommand): else: puzzles.extend(_puzzles) - logger.log_info("PUZZLES %r" % ([p.dbref for p in puzzles])) + logger.log_info("PUZZLES %r" % ([(p.dbref, p.db.puzzle_name) for p in puzzles])) - # Create lookup dict + # Create lookup dict of puzzles by dbref puzzles_dict = dict((puzzle.dbref, puzzle) for puzzle in puzzles) # Check if parts can be combined to solve a puzzle matched_puzzles = dict() for puzzle in puzzles: - puzzleparts = list(sorted(puzzle.db.parts[:], key=lambda p: p['key'])) - parts = list(sorted(puzzle_ingredients[puzzle.db.puzzle_name][:], key=lambda p: p[1]['key'])) - pz = 0 - p = 0 - matched_dbrefparts = set() - while pz < len(puzzleparts) and p < len(parts): - puzzlepart = puzzleparts[pz] - if 'tags' in puzzlepart: - # remove 'tags' as they will prevent equality - del(puzzlepart['tags']) - dbref, part = parts[p] - if part == puzzlepart: - pz += 1 - matched_dbrefparts.add(dbref) - p += 1 + puzzle_protoparts = list(puzzle.db.parts[:]) + # remove tags as they prevent equality + for puzzle_protopart in puzzle_protoparts: + del(puzzle_protopart['tags']) + matched_dbrefparts = [] + parts_dbrefs = puzzlename_tags_dict[puzzle.db.puzzle_name] + for part_dbref in parts_dbrefs: + protopart = puzzle_ingredients[part_dbref] + if protopart in puzzle_protoparts: + puzzle_protoparts.remove(protopart) + matched_dbrefparts.append(part_dbref) else: - if len(puzzleparts) == len(matched_dbrefparts): + if len(puzzle_protoparts) == 0: matched_puzzles[puzzle.dbref] = matched_dbrefparts if len(matched_puzzles) == 0: @@ -506,7 +504,6 @@ class CmdUsePuzzleParts(MuxCommand): caller.msg("You try %s ..." % (puzzle.db.puzzle_name)) # got one, spawn its results - # FIXME: DRY with parts result_names = [] for proto_result in puzzle.db.results: result = spawn(proto_result)[0] @@ -523,6 +520,7 @@ class CmdUsePuzzleParts(MuxCommand): result_names = ', '.join(result_names) caller.msg(puzzle.db.use_success_message) + # TODO: allow custom message for location and channels caller.location.msg_contents( "|c%s|n performs some kind of tribal dance" " and |y%s|n seems to appear from thin air" % ( @@ -554,7 +552,7 @@ class CmdListPuzzleRecipes(MuxCommand): msgf_item = "%2s|c%15s|n: |w%s|n" for recipe in recipes: text.append(msgf_recipe % (recipe.db.puzzle_name, recipe.name, recipe.dbref)) - text.append('Success message: ' + recipe.db.use_success_message) + text.append('Success message:\n' + recipe.db.use_success_message + '\n') text.append('Parts') for protopart in recipe.db.parts[:]: mark = '-' diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index efd2a3cdb0..8620a28326 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -1487,7 +1487,7 @@ class TestPuzzles(CommandTest): [ r"^-+$", r"^Puzzle 'makefire'.*$", - r"^Success message: .*$", + r"^Success message:$", r"^Parts$", r"^.*key: stone$", r"^.*key: flint$", @@ -1609,3 +1609,105 @@ class TestPuzzles(CommandTest): # TODO: results has NPC # TODO: results has Room + + # TODO: parts' location can be different from Character's location + + def test_e2e_interchangeable_parts_and_results(self): + # Parts and Results can be used in multiple puzzles + egg = create_object(key='egg', location=self.char1.location) + flour = create_object(key='flour', location=self.char1.location) + boiling_water = create_object(key='boiling water', location=self.char1.location) + boiled_egg = create_object(key='boiled egg', location=self.char1.location) + dough = create_object(key='dough', location=self.char1.location) + pasta = create_object(key='pasta', location=self.char1.location) + + # Three recipes: + # 1. breakfast: egg + boiling water = boiled egg & boiling water + # 2. dough: egg + flour = dough + # 3. entree: dough + boiling water = pasta & boiling water + # tag interchangeable parts according to their puzzles' name + egg.tags.add('breakfast', category=puzzles._PUZZLES_TAG_CATEGORY) + egg.tags.add('dough', category=puzzles._PUZZLES_TAG_CATEGORY) + dough.tags.add('entree', category=puzzles._PUZZLES_TAG_CATEGORY) + boiling_water.tags.add('breakfast', category=puzzles._PUZZLES_TAG_CATEGORY) + boiling_water.tags.add('entree', category=puzzles._PUZZLES_TAG_CATEGORY) + + # create recipes + recipe1_dbref = self._good_recipe('breakfast', ['egg', 'boiling water'], ['boiled egg', 'boiling water'] , and_destroy_it=False) + recipe2_dbref = self._good_recipe('dough', ['egg', 'flour'], ['dough'] , and_destroy_it=False, expected_count=2) + recipe3_dbref = self._good_recipe('entree', ['dough', 'boiling water'], ['pasta', 'boiling water'] , and_destroy_it=False, expected_count=3) + + # delete protoparts + for obj in [egg, flour, boiling_water, + boiled_egg, dough, pasta]: + obj.delete() + + # arm each puzzle and group its parts + def _group_parts(parts, excluding=set()): + group = dict() + dbrefs = dict() + for o in self.room1.contents: + if o.key in parts and o.dbref not in excluding: + if o.key not in group: + group[o.key] = [] + group[o.key].append(o.dbref) + dbrefs[o.dbref] = o + return group, dbrefs + + self._arm(recipe1_dbref, 'breakfast', ['egg', 'boiling water']) + breakfast_parts, breakfast_dbrefs = _group_parts(['egg', 'boiling water']) + self._arm(recipe2_dbref, 'dough', ['egg', 'flour']) + dough_parts, dough_dbrefs = _group_parts(['egg', 'flour'], excluding=breakfast_dbrefs.keys()) + self._arm(recipe3_dbref, 'entree', ['dough', 'boiling water']) + entree_parts, entree_dbrefs = _group_parts(['dough', 'boiling water'], excluding=set(breakfast_dbrefs.keys() + dough_dbrefs.keys())) + + # create a box so we can put all objects in + # so that they can't be found during puzzle resolution + self.box = create_object(key='box', location=self.char1.location) + def _box_all(): + # print "boxing all\n", "-"*20 + for o in self.room1.contents: + if o not in [self.char1, self.char2, self.exit, + self.obj1, self.obj2, self.box]: + o.location = self.box + # print o.key, o.dbref, "boxed" + else: + # print "skipped", o.key, o.dbref + pass + + def _unbox(dbrefs): + # print "unboxing", dbrefs, "\n", "-"*20 + for o in self.box.contents: + if o.dbref in dbrefs: + o.location = self.room1 + # print "unboxed", o.key, o.dbref + + # solve dough puzzle using breakfast's egg + # and dough's flour. A new dough will be created + _box_all() + _unbox(breakfast_parts.pop('egg') + dough_parts.pop('flour')) + self._use('egg, flour', 'You are a Genius') + + # solve entree puzzle with newly created dough + # and breakfast's boiling water. A new + # boiling water and pasta will be created + _unbox(breakfast_parts.pop('boiling water')) + self._use('boiling water, dough', 'You are a Genius') + + # solve breakfast puzzle with dough's egg + # and newly created boiling water. A new + # boiling water and boiled egg will be created + _unbox(dough_parts.pop('egg')) + self._use('boiling water, egg', 'You are a Genius') + + # solve entree puzzle using entree's dough + # and newly created boiling water. A new + # boiling water and pasta will be created + _unbox(entree_parts.pop('dough')) + self._use('boiling water, dough', 'You are a Genius') + + self._check_room_contents({ + 'boiling water': 1, + 'pasta': 2, + 'boiled egg': 1, + }) From f70b2b94dd458aead67c67847adf9806297367d0 Mon Sep 17 00:00:00 2001 From: Henddher Pedroza Date: Sun, 16 Sep 2018 17:59:47 -0500 Subject: [PATCH 046/493] Honor proto parts and results tags --- evennia/contrib/puzzles.py | 2 +- evennia/contrib/tests.py | 27 +++++++++++++++++++-------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/evennia/contrib/puzzles.py b/evennia/contrib/puzzles.py index 19a291358d..39c8a69765 100644 --- a/evennia/contrib/puzzles.py +++ b/evennia/contrib/puzzles.py @@ -108,7 +108,7 @@ def proto_def(obj, with_tags=True): 'permissions': obj.permissions.all()[:], } if with_tags: - tags = obj.tags.all()[:] + tags = obj.tags.all(return_key_and_category=True) tags.append((_PUZZLES_TAG_MEMBER, _PUZZLES_TAG_CATEGORY)) protodef['tags'] = tags return protodef diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 8620a28326..13e06f209f 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -1186,6 +1186,12 @@ class TestPuzzles(CommandTest): self.stone = create_object(key='stone', location=self.char1.location) self.flint = create_object(key='flint', location=self.char1.location) self.fire = create_object(key='fire', location=self.char1.location) + self.stone.tags.add('tag-stone') + self.stone.tags.add('tag-stone', category='tagcat') + self.flint.tags.add('tag-flint') + self.flint.tags.add('tag-flint', category='tagcat') + self.fire.tags.add('tag-fire') + self.fire.tags.add('tag-fire', category='tagcat') def _assert_msg_matched(self, msg, regexs, re_flags=0): matches = [] @@ -1239,7 +1245,7 @@ class TestPuzzles(CommandTest): matches = self._assert_msg_matched(msg, regexs, re_flags=re.MULTILINE | re.DOTALL) return recipe_dbref - def _check_room_contents(self, expected): + def _check_room_contents(self, expected, check_test_tags=False): by_obj_key = lambda o: o.key room1_contents = sorted(self.room1.contents, key=by_obj_key) for key, grp in itertools.groupby(room1_contents, by_obj_key): @@ -1247,6 +1253,11 @@ class TestPuzzles(CommandTest): grp = list(grp) self.assertEqual(expected[key], len(grp), "Expected %d but got %d for %s" % (expected[key], len(grp), key)) + if check_test_tags: + for gi in grp: + tags = gi.tags.all(return_key_and_category=True) + self.assertIn(('tag-' + gi.key, None), tags) + self.assertIn(('tag-' + gi.key, 'tagcat'), tags) def _arm(self, recipe_dbref, name, parts): regexs = [ @@ -1341,7 +1352,7 @@ class TestPuzzles(CommandTest): # good arm self._arm(recipe_dbref, 'makefire', ['stone', 'flint']) - self._check_room_contents({'stone': 1, 'flint': 1}) + self._check_room_contents({'stone': 1, 'flint': 1}, check_test_tags=True) def _use(self, cmdstr, expmsg): msg = self.call( @@ -1370,7 +1381,7 @@ class TestPuzzles(CommandTest): self._use('stone', 'You have no idea how this can be used') self._use('stone, flint', 'You have no idea how these can be used') self._arm(recipe_dbref, 'makefire', ['stone', 'flint']) - self._check_room_contents({'stone': 2, 'flint': 2}) + self._check_room_contents({'stone': 2, 'flint': 2}, check_test_tags=True) # there are duplicated objects now self._use('stone', 'Which stone. There are many') @@ -1388,7 +1399,7 @@ class TestPuzzles(CommandTest): lambda o: o.key == 'fire' \ and inherits_from(o,'evennia.contrib.puzzles.PuzzlePartObject'), self.room1.contents)))) - self._check_room_contents({'stone': 0, 'flint': 0, 'fire': 1}) + self._check_room_contents({'stone': 0, 'flint': 0, 'fire': 1}, check_test_tags=True) # trying again will fail as it was resolved already # and the parts were destroyed @@ -1398,14 +1409,14 @@ class TestPuzzles(CommandTest): # arm same puzzle twice so there are duplicated parts self._arm(recipe_dbref, 'makefire', ['stone', 'flint']) self._arm(recipe_dbref, 'makefire', ['stone', 'flint']) - self._check_room_contents({'stone': 2, 'flint': 2, 'fire': 1}) + self._check_room_contents({'stone': 2, 'flint': 2, 'fire': 1}, check_test_tags=True) # try solving with multiple parts but incomplete set self._use('1-stone, 2-stone', 'You try to utilize these but nothing happens ... something amiss?') # arm the other puzzle. Their parts are identical self._arm(recipe2_dbref, 'makefire2', ['stone', 'flint']) - self._check_room_contents({'stone': 3, 'flint': 3, 'fire': 1}) + self._check_room_contents({'stone': 3, 'flint': 3, 'fire': 1}, check_test_tags=True) # solve with multiple parts for # multiple puzzles. Both can be solved but @@ -1413,12 +1424,12 @@ class TestPuzzles(CommandTest): self._use( '1-stone, 2-flint, 3-stone, 3-flint', 'Your gears start turning and a bunch of ideas come to your mind ... ') - self._check_room_contents({'stone': 2, 'flint': 2, 'fire': 2}) + self._check_room_contents({'stone': 2, 'flint': 2, 'fire': 2}, check_test_tags=True) # solve all self._use('1-stone, 1-flint', 'You are a Genius') self._use('stone, flint', 'You are a Genius') - self._check_room_contents({'stone': 0, 'flint': 0, 'fire': 4}) + self._check_room_contents({'stone': 0, 'flint': 0, 'fire': 4}, check_test_tags=True) def test_puzzleedit(self): recipe_dbref = self._good_recipe('makefire', ['stone', 'flint'], ['fire'] , and_destroy_it=False) From b747aa0e053728a5fbd2c627f4eefc632cb554ff Mon Sep 17 00:00:00 2001 From: Henddher Pedroza Date: Sun, 16 Sep 2018 18:19:04 -0500 Subject: [PATCH 047/493] Bump up coverage for puzzles module --- evennia/contrib/puzzles.py | 2 +- evennia/contrib/tests.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/evennia/contrib/puzzles.py b/evennia/contrib/puzzles.py index 39c8a69765..0cab7859ac 100644 --- a/evennia/contrib/puzzles.py +++ b/evennia/contrib/puzzles.py @@ -305,7 +305,7 @@ class CmdEditPuzzle(MuxCommand): puzzle = search.search_script(recipe_dbref) if not puzzle or not inherits_from(puzzle[0], PuzzleRecipe): - caller.msg('Invalid puzzle %r' % (recipe_dbref)) + caller.msg('%s(%s) is not a puzzle' % (puzzle[0].name, recipe_dbref)) return puzzle = puzzle[0] diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 13e06f209f..4810ec9d09 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -1440,17 +1440,23 @@ class TestPuzzles(CommandTest): self.fire.delete() def _puzzleedit(swt, dbref, args, expmsg): + if (swt is None) and (dbref is None) and (args is None): + cmdstr = '' + else: + cmdstr = '%s %s%s' % (swt, dbref, args) self.call( puzzles.CmdEditPuzzle(), - '%s %s%s' % (swt, dbref, args), + cmdstr, expmsg, caller=self.char1 ) # bad syntax + _puzzleedit(None, None, None, "A puzzle recipe's #dbref must be specified.\nUsage: @puzzleedit") _puzzleedit('', '1', '', "A puzzle recipe's #dbref must be specified.\nUsage: @puzzleedit") _puzzleedit('', '', '', "A puzzle recipe's #dbref must be specified.\nUsage: @puzzleedit") _puzzleedit('', recipe_dbref, 'dummy', "A puzzle recipe's #dbref must be specified.\nUsage: @puzzleedit") + _puzzleedit('', self.script.dbref, '', 'Script(#1) is not a puzzle') # no permissions _puzzleedit('', recipe_dbref, '/use_success_message = Yes!', "You don't have permission") From f6b0ddca94a812fc68643e5980435c0892e6ae9e Mon Sep 17 00:00:00 2001 From: Henddher Pedroza Date: Sun, 16 Sep 2018 19:04:07 -0500 Subject: [PATCH 048/493] Add total recipes/armed-puzzles to @lspuzzlerecipes and @lsarmedpuzzles --- evennia/contrib/puzzles.py | 6 ++++++ evennia/contrib/tests.py | 8 +++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/evennia/contrib/puzzles.py b/evennia/contrib/puzzles.py index 0cab7859ac..2d317efe46 100644 --- a/evennia/contrib/puzzles.py +++ b/evennia/contrib/puzzles.py @@ -565,6 +565,9 @@ class CmdListPuzzleRecipes(MuxCommand): for k, v in protoresult.items(): text.append(msgf_item % (mark, k, v)) mark = '' + else: + text.append(div) + text.append('%d puzzle(s).' % (len(recipes))) text.append(div) caller.msg('\n'.join(text)) @@ -601,6 +604,9 @@ class CmdListArmedPuzzles(MuxCommand): text.append(msgf_item % ( item.name, item.dbref, item.location.name, item.location.dbref)) + else: + text.append(div) + text.append('%d armed puzzle(s).' % (len(armed_puzzles))) text.append(div) caller.msg('\n'.join(text)) diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 4810ec9d09..2aaa614ae8 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -1485,7 +1485,8 @@ class TestPuzzles(CommandTest): msg, [ r"^-+$", - r"^-+$", + r"^0 puzzle\(s\)\.$", + r"-+$", ], re.MULTILINE | re.DOTALL ) @@ -1513,6 +1514,8 @@ class TestPuzzles(CommandTest): r"^.*key: stone$", r"^.*key: flint$", r"^-+$", + r"^1 puzzle\(s\)\.$", + r"^-+$", ], re.MULTILINE | re.DOTALL ) @@ -1526,6 +1529,8 @@ class TestPuzzles(CommandTest): msg, [ r"^-+$", + r"^-+$", + r"^0 armed puzzle\(s\)\.$", r"^-+$" ], re.MULTILINE | re.DOTALL @@ -1545,6 +1550,7 @@ class TestPuzzles(CommandTest): r"^Puzzle name: makefire$", r"^.*stone.* at \s+ Room.*$", r"^.*flint.* at \s+ Room.*$", + r"^1 armed puzzle\(s\)\.$", r"^-+$", ], re.MULTILINE | re.DOTALL From d44552a2292c396d6268181e9968ad773e1963d7 Mon Sep 17 00:00:00 2001 From: Henddher Pedroza Date: Sun, 16 Sep 2018 19:06:48 -0500 Subject: [PATCH 049/493] Minor msg editing/formatting --- evennia/contrib/puzzles.py | 4 ++-- evennia/contrib/tests.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/evennia/contrib/puzzles.py b/evennia/contrib/puzzles.py index 2d317efe46..4816bdde0a 100644 --- a/evennia/contrib/puzzles.py +++ b/evennia/contrib/puzzles.py @@ -567,7 +567,7 @@ class CmdListPuzzleRecipes(MuxCommand): mark = '' else: text.append(div) - text.append('%d puzzle(s).' % (len(recipes))) + text.append('Found |r%d|n puzzle(s).' % (len(recipes))) text.append(div) caller.msg('\n'.join(text)) @@ -606,7 +606,7 @@ class CmdListArmedPuzzles(MuxCommand): item.location.name, item.location.dbref)) else: text.append(div) - text.append('%d armed puzzle(s).' % (len(armed_puzzles))) + text.append('Found |r%d|n armed puzzle(s).' % (len(armed_puzzles))) text.append(div) caller.msg('\n'.join(text)) diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 2aaa614ae8..a32d338b15 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -1485,7 +1485,7 @@ class TestPuzzles(CommandTest): msg, [ r"^-+$", - r"^0 puzzle\(s\)\.$", + r"^Found 0 puzzle\(s\)\.$", r"-+$", ], re.MULTILINE | re.DOTALL @@ -1514,7 +1514,7 @@ class TestPuzzles(CommandTest): r"^.*key: stone$", r"^.*key: flint$", r"^-+$", - r"^1 puzzle\(s\)\.$", + r"^Found 1 puzzle\(s\)\.$", r"^-+$", ], re.MULTILINE | re.DOTALL @@ -1530,7 +1530,7 @@ class TestPuzzles(CommandTest): [ r"^-+$", r"^-+$", - r"^0 armed puzzle\(s\)\.$", + r"^Found 0 armed puzzle\(s\)\.$", r"^-+$" ], re.MULTILINE | re.DOTALL @@ -1550,7 +1550,7 @@ class TestPuzzles(CommandTest): r"^Puzzle name: makefire$", r"^.*stone.* at \s+ Room.*$", r"^.*flint.* at \s+ Room.*$", - r"^1 armed puzzle\(s\)\.$", + r"^Found 1 armed puzzle\(s\)\.$", r"^-+$", ], re.MULTILINE | re.DOTALL From 8cd3634fa79483190b84f7a5642fbb81a2d3f012 Mon Sep 17 00:00:00 2001 From: Henddher Pedroza Date: Mon, 17 Sep 2018 20:57:11 -0500 Subject: [PATCH 050/493] Add use_success_location_message so contents of room 'see' puzzle solver succeeding. Add parts are interchangeable warning confirmation when new puzzle-recipe matches existing one. --- evennia/contrib/puzzles.py | 64 ++++++++++++++++++++++++++++---------- evennia/contrib/tests.py | 11 +++++-- 2 files changed, 57 insertions(+), 18 deletions(-) diff --git a/evennia/contrib/puzzles.py b/evennia/contrib/puzzles.py index 4816bdde0a..47166c88f9 100644 --- a/evennia/contrib/puzzles.py +++ b/evennia/contrib/puzzles.py @@ -89,6 +89,7 @@ _PUZZLES_TAG_MEMBER = 'puzzle_member' _PUZZLE_DEFAULT_FAIL_USE_MESSAGE = 'You try to utilize %s but nothing happens ... something amiss?' _PUZZLE_DEFAULT_SUCCESS_USE_MESSAGE = 'You are a Genius!!!' +_PUZZLE_DEFAULT_SUCCESS_USE_LOCATION_MESSAGE = "|c{caller}|n performs some kind of tribal dance and |y{result_names}|n seems to appear from thin air" # ----------- UTILITY FUNCTIONS ------------ @@ -114,13 +115,17 @@ def proto_def(obj, with_tags=True): return protodef # Colorize the default success message -_i = 0 -_colors = ['|r', '|g', '|y'] -_msg = [] -for l in _PUZZLE_DEFAULT_SUCCESS_USE_MESSAGE: - _msg += _colors[_i] + l - _i = (_i + 1) % len(_colors) -_PUZZLE_DEFAULT_SUCCESS_USE_MESSAGE = ''.join(_msg) + '|n' +def _colorize_message(msg): + _i = 0 + _colors = ['|r', '|g', '|y'] + _msg = [] + for l in msg: + _msg += _colors[_i] + l + _i = (_i + 1) % len(_colors) + msg = ''.join(_msg) + '|n' + return msg + +_PUZZLE_DEFAULT_SUCCESS_USE_MESSAGE = _colorize_message(_PUZZLE_DEFAULT_SUCCESS_USE_MESSAGE) # ------------------------------------------ @@ -149,6 +154,7 @@ class PuzzleRecipe(DefaultScript): self.db.results = tuple(results) self.tags.add(_PUZZLES_TAG_RECIPE, category=_PUZZLES_TAG_CATEGORY) self.db.use_success_message = _PUZZLE_DEFAULT_SUCCESS_USE_MESSAGE + self.db.use_success_location_message = _PUZZLE_DEFAULT_SUCCESS_USE_LOCATION_MESSAGE class CmdCreatePuzzleRecipe(MuxCommand): @@ -168,6 +174,9 @@ class CmdCreatePuzzleRecipe(MuxCommand): locks = 'cmd:perm(puzzle) or perm(Builder)' help_category = 'Puzzles' + confirm = True + default_confirm = 'no' + def func(self): caller = self.caller @@ -182,9 +191,25 @@ class CmdCreatePuzzleRecipe(MuxCommand): caller.msg('Invalid puzzle name %r.' % puzzle_name) return - # TODO: if there is another puzzle with same name + # if there is another puzzle with same name # warn user that parts and results will be # interchangable + _puzzles = search.search_script_attribute( + key='puzzle_name', + value=puzzle_name + ) + _puzzles = list(filter(lambda p: isinstance(p, PuzzleRecipe), _puzzles)) + if _puzzles: + confirm = 'There are %d puzzles with the same name.\n' % len(_puzzles) \ + + 'Its parts and results will be interchangeable.\n' \ + + 'Continue yes/[no]? ' + answer = '' + while answer.strip().lower() not in ('y', 'yes', 'n', 'no'): + answer = yield(confirm) + answer = self.default_confirm if answer == '' else answer + if answer.strip().lower() in ('n', 'no'): + caller.msg('Cancelled: no puzzle created.') + return def is_valid_obj_location(obj): valid = True @@ -275,14 +300,16 @@ class CmdEditPuzzle(MuxCommand): Usage: @puzzleedit[/delete] <#dbref> @puzzleedit <#dbref>/use_success_message = + @puzzleedit <#dbref>/use_success_location_message = Switches: delete - deletes the recipe. Existing parts and results aren't modified + use_success_location_message containing {result_names} and {caller} will automatically be replaced with correct values. Both are optional. + """ key = '@puzzleedit' - # FIXME: permissions for scripts? locks = 'cmd:perm(puzzleedit) or perm(Builder)' help_category = 'Puzzles' @@ -332,7 +359,13 @@ class CmdEditPuzzle(MuxCommand): caller.msg( "%s use_success_message = %s\n" % (puzzle_name_id, puzzle.db.use_success_message) ) - return + return + elif attr == 'use_success_location_message': + puzzle.db.use_success_location_message = self.rhs + caller.msg( + "%s use_success_location_message = %s\n" % (puzzle_name_id, puzzle.db.use_success_location_message) + ) + return class CmdArmPuzzle(MuxCommand): @@ -341,7 +374,6 @@ class CmdArmPuzzle(MuxCommand): """ key = '@armpuzzle' - # FIXME: permissions for scripts? locks = 'cmd:perm(armpuzzle) or perm(Builder)' help_category = 'Puzzles' @@ -520,11 +552,10 @@ class CmdUsePuzzleParts(MuxCommand): result_names = ', '.join(result_names) caller.msg(puzzle.db.use_success_message) - # TODO: allow custom message for location and channels caller.location.msg_contents( - "|c%s|n performs some kind of tribal dance" - " and |y%s|n seems to appear from thin air" % ( - caller, result_names), exclude=(caller,) + puzzle.db.use_success_location_message.format( + caller=caller, result_names=result_names), + exclude=(caller,) ) @@ -552,7 +583,8 @@ class CmdListPuzzleRecipes(MuxCommand): msgf_item = "%2s|c%15s|n: |w%s|n" for recipe in recipes: text.append(msgf_recipe % (recipe.db.puzzle_name, recipe.name, recipe.dbref)) - text.append('Success message:\n' + recipe.db.use_success_message + '\n') + text.append('Success Caller message:\n' + recipe.db.use_success_message + '\n') + text.append('Success Location message:\n' + recipe.db.use_success_location_message + '\n') text.append('Parts') for protopart in recipe.db.parts[:]: mark = '-' diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index a32d338b15..744251774c 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -1426,8 +1426,11 @@ class TestPuzzles(CommandTest): 'Your gears start turning and a bunch of ideas come to your mind ... ') self._check_room_contents({'stone': 2, 'flint': 2, 'fire': 2}, check_test_tags=True) + self.room1.msg_contents = Mock() + # solve all self._use('1-stone, 1-flint', 'You are a Genius') + self.room1.msg_contents.assert_called_once_with('|cChar|n performs some kind of tribal dance and |yfire|n seems to appear from thin air', exclude=(self.char1,)) self._use('stone, flint', 'You are a Genius') self._check_room_contents({'stone': 0, 'flint': 0, 'fire': 4}, check_test_tags=True) @@ -1466,10 +1469,13 @@ class TestPuzzles(CommandTest): puzzle = search.search_script(recipe_dbref)[0] puzzle.locks.add('control:id(%s)' % self.char1.dbref[1:]) - # edit use_success_message + # edit use_success_message and use_success_location_message _puzzleedit('', recipe_dbref, '/use_success_message = Yes!', 'makefire(%s) use_success_message = Yes!' % recipe_dbref) + _puzzleedit('', recipe_dbref, '/use_success_location_message = {result_names} Yeah baby! {caller}', 'makefire(%s) use_success_location_message = {result_names} Yeah baby! {caller}' % recipe_dbref) self._arm(recipe_dbref, 'makefire', ['stone', 'flint']) + self.room1.msg_contents = Mock() self._use('stone, flint', 'Yes!') + self.room1.msg_contents.assert_called_once_with('fire Yeah baby! Char', exclude=(self.char1,)) # delete _puzzleedit('/delete', recipe_dbref, '', 'makefire(%s) was deleted' % recipe_dbref) @@ -1505,7 +1511,8 @@ class TestPuzzles(CommandTest): [ r"^-+$", r"^Puzzle 'makefire'.*$", - r"^Success message:$", + r"^Success Caller message:$", + r"^Success Location message:$", r"^Parts$", r"^.*key: stone$", r"^.*key: flint$", From 2700b4409e5625035f730a1fd0002ca1fdc8073d Mon Sep 17 00:00:00 2001 From: Henddher Pedroza Date: Tue, 18 Sep 2018 22:46:01 -0500 Subject: [PATCH 051/493] Add add/del(part) and add/del(results) switches to @puzzleedit to modify recipes --- evennia/contrib/puzzles.py | 96 ++++++++++++++++++++++++++++++++++++-- evennia/contrib/tests.py | 72 ++++++++++++++++++++++++++-- 2 files changed, 160 insertions(+), 8 deletions(-) diff --git a/evennia/contrib/puzzles.py b/evennia/contrib/puzzles.py index 47166c88f9..1738954066 100644 --- a/evennia/contrib/puzzles.py +++ b/evennia/contrib/puzzles.py @@ -301,12 +301,22 @@ class CmdEditPuzzle(MuxCommand): @puzzleedit[/delete] <#dbref> @puzzleedit <#dbref>/use_success_message = @puzzleedit <#dbref>/use_success_location_message = + @puzzleedit[/addpart] <#dbref> = + @puzzleedit[/delpart] <#dbref> = + @puzzleedit[/addresult] <#dbref> = + @puzzleedit[/delresult] <#dbref> = Switches: + addpart - adds parts to the puzzle + delpart - removes parts from the puzzle + addresult - adds results to the puzzle + delresult - removes results from the puzzle delete - deletes the recipe. Existing parts and results aren't modified use_success_location_message containing {result_names} and {caller} will automatically be replaced with correct values. Both are optional. + When removing parts/results, it's possible to remove all. + """ key = '@puzzleedit' @@ -314,11 +324,11 @@ class CmdEditPuzzle(MuxCommand): help_category = 'Puzzles' def func(self): - _USAGE = "Usage: @puzzleedit[/switches] [/attribute = ]" + self._USAGE = "Usage: @puzzleedit[/switches] [/attribute = ]" caller = self.caller if not self.lhslist: - caller.msg(_USAGE) + caller.msg(self._USAGE) return if '/' in self.lhslist[0]: @@ -327,7 +337,7 @@ class CmdEditPuzzle(MuxCommand): recipe_dbref = self.lhslist[0] if not utils.dbref(recipe_dbref): - caller.msg("A puzzle recipe's #dbref must be specified.\n" + _USAGE) + caller.msg("A puzzle recipe's #dbref must be specified.\n" + self._USAGE) return puzzle = search.search_script(recipe_dbref) @@ -347,6 +357,34 @@ class CmdEditPuzzle(MuxCommand): caller.msg('%s was deleted' % puzzle_name_id) return + elif 'addpart' in self.switches: + objs = self._get_objs() + if objs: + added = self._add_parts(objs, puzzle) + caller.msg('%s were added to parts' % (', '.join(added))) + return + + elif 'delpart' in self.switches: + objs = self._get_objs() + if objs: + removed = self._remove_parts(objs, puzzle) + caller.msg('%s were removed from parts' % (', '.join(removed))) + return + + elif 'addresult' in self.switches: + objs = self._get_objs() + if objs: + added = self._add_results(objs, puzzle) + caller.msg('%s were added to results' % (', '.join(added))) + return + + elif 'delresult' in self.switches: + objs = self._get_objs() + if objs: + removed = self._remove_results(objs, puzzle) + caller.msg('%s were removed from results' % (', '.join(removed))) + return + else: # edit attributes @@ -367,6 +405,58 @@ class CmdEditPuzzle(MuxCommand): ) return + def _get_objs(self): + if not self.rhslist: + self.caller.msg(self._USAGE) + return + objs = [] + for o in self.rhslist: + obj = self.caller.search(o) + if obj: + objs.append(obj) + return objs + + def _add_objs_to(self, objs, to): + """Adds propto objs to the given set (parts or results)""" + added = [] + toobjs = list(to[:]) + for obj in objs: + protoobj = proto_def(obj) + toobjs.append(protoobj) + added.append(obj.key) + return added, toobjs + + def _remove_objs_from(self, objs, frm): + """Removes propto objs from the given set (parts or results)""" + removed = [] + fromobjs = list(frm[:]) + for obj in objs: + protoobj = proto_def(obj) + if protoobj in fromobjs: + fromobjs.remove(protoobj) + removed.append(obj.key) + return removed, fromobjs + + def _add_parts(self, objs, puzzle): + added, toobjs = self._add_objs_to(objs, puzzle.db.parts) + puzzle.db.parts = tuple(toobjs) + return added + + def _remove_parts(self, objs, puzzle): + removed, fromobjs = self._remove_objs_from(objs, puzzle.db.parts) + puzzle.db.parts = tuple(fromobjs) + return removed + + def _add_results(self, objs, puzzle): + added, toobjs = self._add_objs_to(objs, puzzle.db.results) + puzzle.db.results = tuple(toobjs) + return added + + def _remove_results(self, objs, puzzle): + removed, fromobjs = self._remove_objs_from(objs, puzzle.db.results) + puzzle.db.results = tuple(fromobjs) + return removed + class CmdArmPuzzle(MuxCommand): """ diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 744251774c..6eca07289b 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -1437,11 +1437,6 @@ class TestPuzzles(CommandTest): def test_puzzleedit(self): recipe_dbref = self._good_recipe('makefire', ['stone', 'flint'], ['fire'] , and_destroy_it=False) - # delete proto parts and proto results - self.stone.delete() - self.flint.delete() - self.fire.delete() - def _puzzleedit(swt, dbref, args, expmsg): if (swt is None) and (dbref is None) and (args is None): cmdstr = '' @@ -1454,6 +1449,11 @@ class TestPuzzles(CommandTest): caller=self.char1 ) + # delete proto parts and proto results + self.stone.delete() + self.flint.delete() + self.fire.delete() + # bad syntax _puzzleedit(None, None, None, "A puzzle recipe's #dbref must be specified.\nUsage: @puzzleedit") _puzzleedit('', '1', '', "A puzzle recipe's #dbref must be specified.\nUsage: @puzzleedit") @@ -1472,6 +1472,7 @@ class TestPuzzles(CommandTest): # edit use_success_message and use_success_location_message _puzzleedit('', recipe_dbref, '/use_success_message = Yes!', 'makefire(%s) use_success_message = Yes!' % recipe_dbref) _puzzleedit('', recipe_dbref, '/use_success_location_message = {result_names} Yeah baby! {caller}', 'makefire(%s) use_success_location_message = {result_names} Yeah baby! {caller}' % recipe_dbref) + self._arm(recipe_dbref, 'makefire', ['stone', 'flint']) self.room1.msg_contents = Mock() self._use('stone, flint', 'Yes!') @@ -1481,6 +1482,67 @@ class TestPuzzles(CommandTest): _puzzleedit('/delete', recipe_dbref, '', 'makefire(%s) was deleted' % recipe_dbref) self._assert_no_recipes() + def test_puzzleedit_add_remove_parts_results(self): + recipe_dbref = self._good_recipe('makefire', ['stone', 'flint'], ['fire'] , and_destroy_it=False) + + def _puzzleedit(swt, dbref, rhslist, expmsg): + cmdstr = '%s %s = %s' % (swt, dbref, ', '.join(rhslist)) + self.call( + puzzles.CmdEditPuzzle(), + cmdstr, + expmsg, + caller=self.char1 + ) + + red_stone = create_object(key='red stone', location=self.char1.location) + smoke = create_object(key='smoke', location=self.char1.location) + + _puzzleedit('/addresult', recipe_dbref, ['smoke'], 'smoke were added to results') + _puzzleedit('/addpart', recipe_dbref, ['red stone', 'stone'], 'red stone, stone were added to parts') + + # create a box so we can put all objects in + # so that they can't be found during puzzle resolution + self.box = create_object(key='box', location=self.char1.location) + def _box_all(): + for o in self.room1.contents: + if o not in [self.char1, self.char2, self.exit, + self.obj1, self.obj2, self.box]: + o.location = self.box + _box_all() + + self._arm(recipe_dbref, 'makefire', ['stone', 'flint', 'red stone', 'stone']) + self._check_room_contents({ + 'stone': 2, + 'red stone': 1, + 'flint': 1, + }) + self._use('1-stone, flint', 'You try to utilize these but nothing happens ... something amiss?') + self._use('1-stone, flint, red stone, 3-stone', 'You are a Genius') + self._check_room_contents({ + 'smoke': 1, + 'fire': 1 + }) + _box_all() + + self.fire.location = self.room1 + self.stone.location = self.room1 + + _puzzleedit('/delresult', recipe_dbref, ['fire'], 'fire were removed from results') + _puzzleedit('/delpart', recipe_dbref, ['stone', 'stone'], 'stone, stone were removed from parts') + + _box_all() + + self._arm(recipe_dbref, 'makefire', ['flint', 'red stone']) + self._check_room_contents({ + 'red stone': 1, + 'flint': 1, + }) + self._use('red stone, flint', 'You are a Genius') + self._check_room_contents({ + 'smoke': 1, + 'fire': 0 + }) + def test_lspuzzlerecipes_lsarmedpuzzles(self): msg = self.call( puzzles.CmdListPuzzleRecipes(), From f9b636676d4be64e3a5141df4c6787f2a21ff848 Mon Sep 17 00:00:00 2001 From: Johnny Date: Mon, 1 Oct 2018 20:12:24 +0000 Subject: [PATCH 052/493] Extends normalize_username() function to strip excessive spaces. --- evennia/accounts/accounts.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/evennia/accounts/accounts.py b/evennia/accounts/accounts.py index 2c33e5c1f8..07fb8f318c 100644 --- a/evennia/accounts/accounts.py +++ b/evennia/accounts/accounts.py @@ -10,7 +10,7 @@ character object, so you should customize that instead for most things). """ - +import re import time from django.conf import settings from django.contrib.auth import password_validation @@ -359,6 +359,27 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)): puppet = property(__get_single_puppet) # utility methods + @classmethod + def normalize_username(cls, username): + """ + Django: Applies NFKC Unicode normalization to usernames so that visually + identical characters with different Unicode code points are considered + identical. + + (This deals with the Turkish "i" problem and similar + annoyances. Only relevant if you go out of your way to allow Unicode + usernames though-- Evennia accepts ASCII by default.) + + In this case we're simply piggybacking on this feature to apply + additional normalization per Evennia's standards. + """ + username = super(DefaultAccount, cls).normalize_username(username) + + # strip excessive spaces in accountname + username = re.sub(r"\s+", " ", username).strip() + + return username + @classmethod def validate_password(cls, password, account=None): """ From c5b7577021200e8958c932a1f8a6e884f41bbd01 Mon Sep 17 00:00:00 2001 From: Johnny Date: Mon, 1 Oct 2018 21:24:33 +0000 Subject: [PATCH 053/493] Adds username normalization/validation and authentication methods to Account class. --- evennia/accounts/accounts.py | 110 ++++++++++++++++++++++++++++++++++- evennia/accounts/tests.py | 29 +++++++++ evennia/server/validators.py | 35 +++++++++++ evennia/settings_default.py | 22 +++++++ 4 files changed, 194 insertions(+), 2 deletions(-) diff --git a/evennia/accounts/accounts.py b/evennia/accounts/accounts.py index 07fb8f318c..8da9a7ea9b 100644 --- a/evennia/accounts/accounts.py +++ b/evennia/accounts/accounts.py @@ -13,9 +13,10 @@ instead for most things). import re import time from django.conf import settings -from django.contrib.auth import password_validation -from django.core.exceptions import ValidationError +from django.contrib.auth import authenticate, password_validation +from django.core.exceptions import ImproperlyConfigured, ValidationError from django.utils import timezone +from django.utils.module_loading import import_string from evennia.typeclasses.models import TypeclassBase from evennia.accounts.manager import AccountManager from evennia.accounts.models import AccountDB @@ -359,6 +360,74 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)): puppet = property(__get_single_puppet) # utility methods + @classmethod + def get_username_validators(cls, validator_config=getattr(settings, 'AUTH_USERNAME_VALIDATORS', [])): + """ + Retrieves and instantiates validators for usernames. + + Args: + validator_config (list): List of dicts comprising the battery of + validators to apply to a username. + + Returns: + validators (list): List of instantiated Validator objects. + """ + + objs = [] + for validator in validator_config: + try: + klass = import_string(validator['NAME']) + except ImportError: + msg = "The module in NAME could not be imported: %s. Check your AUTH_USERNAME_VALIDATORS setting." + raise ImproperlyConfigured(msg % validator['NAME']) + objs.append(klass(**validator.get('OPTIONS', {}))) + return objs + + @classmethod + def authenticate(cls, username, password, ip=None): + """ + Checks the given username/password against the database to see if the + credentials are valid. + + Note that this simply checks credentials and returns a valid reference + to the user-- it does not log them in! + + To finish the job: + After calling this from a Command, associate the account with a Session: + - session.sessionhandler.login(session, account) + + ...or after calling this from a View, associate it with an HttpRequest: + - django.contrib.auth.login(account, request) + + Args: + username (str): Username of account + password (str): Password of account + ip (str, optional): IP address of client + + Returns: + account (DefaultAccount, None): Account whose credentials were + provided if not banned. + errors (list): Error messages of any failures. + + """ + errors = [] + if ip: ip = str(ip) + + # Authenticate and get Account object + account = authenticate(username=username, password=password) + if not account: + # User-facing message + errors.append('Username and/or password is incorrect.') + + # System log message + logger.log_sec('Authentication Failure: %s (IP: %s).' % (username, ip)) + + return None, errors + + # Account successfully authenticated + logger.log_sec('Authentication Success: %s (IP: %s).' % (account, ip)) + return account, errors + @classmethod def normalize_username(cls, username): """ @@ -379,6 +448,43 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)): username = re.sub(r"\s+", " ", username).strip() return username + + @classmethod + def validate_username(cls, username): + """ + Checks the given username against the username validator associated with + Account objects, and also checks the database to make sure it is unique. + + Args: + username (str): Username to validate + + Returns: + valid (bool): Whether or not the password passed validation + errors (list): Error messages of any failures + + """ + valid = [] + errors = [] + + # Make sure we're at least using the default validator + validators = cls.get_username_validators() + if not validators: + validators = [cls.username_validator] + + # Try username against all enabled validators + for validator in validators: + try: + valid.append(not validator(username)) + except ValidationError as e: + valid.append(False) + [errors.append(x) for x in e.messages] + + # Disqualify if any check failed + if False in valid: + valid = False + else: valid = True + + return valid, errors @classmethod def validate_password(cls, password, account=None): diff --git a/evennia/accounts/tests.py b/evennia/accounts/tests.py index 2855dd0ca2..f1a9f16c28 100644 --- a/evennia/accounts/tests.py +++ b/evennia/accounts/tests.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + from mock import Mock from random import randint from unittest import TestCase @@ -59,6 +61,33 @@ class TestDefaultAccount(TestCase): self.s1 = Session() self.s1.puppet = None self.s1.sessid = 0 + + self.password = "testpassword" + self.account = create.create_account("TestAccount%s" % randint(100000, 999999), email="test@test.com", password=self.password, typeclass=DefaultAccount) + + def test_authentication(self): + "Confirm Account authentication method is authenticating/denying users." + # Valid credentials + obj, errors = DefaultAccount.authenticate(self.account.name, self.password) + self.assertTrue(obj, 'Account did not authenticate given valid credentials.') + + # Invalid credentials + obj, errors = DefaultAccount.authenticate(self.account.name, 'xyzzy') + self.assertFalse(obj, 'Account authenticated using invalid credentials.') + + def test_username_validation(self): + "Check username validators deny relevant usernames" + # Should not accept Unicode by default, lest users pick names like this + result, error = DefaultAccount.validate_username('¯\_(ツ)_/¯') + self.assertFalse(result, "Validator allowed kanji in username.") + + # Should not allow duplicate username + result, error = DefaultAccount.validate_username(self.account.name) + self.assertFalse(result, "Duplicate username should not have passed validation.") + + # Should not allow username too short + result, error = DefaultAccount.validate_username('xx') + self.assertFalse(result, "2-character username passed validation.") def test_password_validation(self): "Check password validators deny bad passwords" diff --git a/evennia/server/validators.py b/evennia/server/validators.py index b10f990a8a..bccbde6b51 100644 --- a/evennia/server/validators.py +++ b/evennia/server/validators.py @@ -1,7 +1,42 @@ +from django.conf import settings from django.core.exceptions import ValidationError from django.utils.translation import gettext as _ +from evennia.accounts.models import AccountDB import re +class EvenniaUsernameAvailabilityValidator: + """ + Checks to make sure a given username is not taken or otherwise reserved. + """ + + def __call__(self, username): + """ + Validates a username to make sure it is not in use or reserved. + + Args: + username (str): Username to validate + + Returns: + None (None): None if password successfully validated, + raises ValidationError otherwise. + + """ + + # Check guest list + if settings.GUEST_LIST and username.lower() in (guest.lower() for guest in settings.GUEST_LIST): + raise ValidationError( + _('Sorry, that username is reserved.'), + code='evennia_username_reserved', + ) + + # Check database + exists = AccountDB.objects.filter(username__iexact=username).exists() + if exists: + raise ValidationError( + _('Sorry, that username is already taken.'), + code='evennia_username_taken', + ) + class EvenniaPasswordValidator: def __init__(self, regex=r"^[\w. @+\-',]+$", policy="Password should contain a mix of letters, spaces, digits and @/./+/-/_/'/, only."): diff --git a/evennia/settings_default.py b/evennia/settings_default.py index 9efbb6314b..04c928ac99 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -809,6 +809,28 @@ AUTH_PASSWORD_VALIDATORS = [ {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'}, {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'}, {'NAME': 'evennia.server.validators.EvenniaPasswordValidator'}] + +# Username validation plugins +AUTH_USERNAME_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.validators.ASCIIUsernameValidator', + }, + { + 'NAME': 'django.core.validators.MinLengthValidator', + 'OPTIONS': { + 'limit_value': 3, + } + }, + { + 'NAME': 'django.core.validators.MaxLengthValidator', + 'OPTIONS': { + 'limit_value': 30, + } + }, + { + 'NAME': 'evennia.server.validators.EvenniaUsernameAvailabilityValidator', + }, +] # Use a custom test runner that just tests Evennia-specific apps. TEST_RUNNER = 'evennia.server.tests.EvenniaTestSuiteRunner' From 3834c36b3ba3cc563463a211292f2bbe8b7c7f07 Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 2 Oct 2018 00:00:40 +0200 Subject: [PATCH 054/493] Start updating CHANGELOG for new version --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index afe5ad1ba7..43b6a27a62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## Evennia 0.9 (2019) + +Update to Python 3 + +- Use `python3 -m venv ` +- Use `python3 -m pdb - - + + From eb1d89d953bacc293bc3bbdd3ecb25490a7ed34b Mon Sep 17 00:00:00 2001 From: Johnny Date: Thu, 4 Oct 2018 00:32:38 +0000 Subject: [PATCH 063/493] Adds template tag to override body. --- evennia/web/website/templates/website/base.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/evennia/web/website/templates/website/base.html b/evennia/web/website/templates/website/base.html index 556aad0306..2004425849 100644 --- a/evennia/web/website/templates/website/base.html +++ b/evennia/web/website/templates/website/base.html @@ -29,6 +29,8 @@ {{game_name}} - {% if flatpage %}{{flatpage.title}}{% else %}{% block titleblock %}{{page_title}}{% endblock %}{% endif %} + {% block body %} + {% include "website/_menu.html" %}
@@ -53,6 +55,8 @@
{% endblock %} + + {% endblock %} From cc2b9f22aa5e67780574fa5c2f1743e273478c3a Mon Sep 17 00:00:00 2001 From: Johnny Date: Thu, 4 Oct 2018 00:33:32 +0000 Subject: [PATCH 064/493] Fixes failure to display error messages and display form as standalone. --- .../templates/website/registration/login.html | 73 ++++++++++--------- 1 file changed, 39 insertions(+), 34 deletions(-) diff --git a/evennia/web/website/templates/website/registration/login.html b/evennia/web/website/templates/website/registration/login.html index 8b2213a251..2aa32d5cff 100644 --- a/evennia/web/website/templates/website/registration/login.html +++ b/evennia/web/website/templates/website/registration/login.html @@ -4,44 +4,49 @@ Login {% endblock %} -{% block content %} +{% block body %} {% load addclass %} - -
-
-
-
-

Login

-
- {% if user.is_authenticated %} -

You are already logged in!

- {% else %} - {% if form.has_errors %} -

Your username and password didn't match. Please try again.

- {% endif %} - -
- {% csrf_token %} - -
- - {{ form.username | addclass:"form-control" }} -
- -
- - {{ form.password | addclass:"form-control" }} -
- -
- - -
-
+
+
+
+
+
+

Login

+
+ {% if user.is_authenticated %} + + {% else %} + {% if form.errors %} + + {% endif %} + {% endif %} + + {% if not user.is_authenticated %} +
+ {% csrf_token %} + +
+ + {{ form.username | addclass:"form-control" }} +
+ +
+ + {{ form.password | addclass:"form-control" }} +
+ +
+
+ + +
+
+ + {% endif %} +
-{% endif %} {% endblock %} From bebd621bd5437f61222dd02f3c2f78c600606441 Mon Sep 17 00:00:00 2001 From: Johnny Date: Thu, 4 Oct 2018 17:19:52 +0000 Subject: [PATCH 065/493] Adds link to password reset form. --- .../web/website/templates/website/registration/login.html | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/evennia/web/website/templates/website/registration/login.html b/evennia/web/website/templates/website/registration/login.html index 2aa32d5cff..87f5d7b9f7 100644 --- a/evennia/web/website/templates/website/registration/login.html +++ b/evennia/web/website/templates/website/registration/login.html @@ -35,6 +35,12 @@ Login {{ form.password | addclass:"form-control" }}
+ +
+
+ +
Sign Up
+

From 4fc7318e4c3eeb5182d268882a4b7674d4fb0a9e Mon Sep 17 00:00:00 2001 From: Johnny Date: Thu, 4 Oct 2018 18:17:32 +0000 Subject: [PATCH 066/493] Updates style of password reset forms to use Bootstrap instead of Django Admin. --- .../registration/password_reset_complete.html | 31 +++++++++++ .../registration/password_reset_confirm.html | 55 +++++++++++++++++++ .../registration/password_reset_done.html | 34 ++++++++++++ .../registration/password_reset_email.html | 15 +++++ .../registration/password_reset_form.html | 48 ++++++++++++++++ 5 files changed, 183 insertions(+) create mode 100644 evennia/web/website/templates/website/registration/password_reset_complete.html create mode 100644 evennia/web/website/templates/website/registration/password_reset_confirm.html create mode 100644 evennia/web/website/templates/website/registration/password_reset_done.html create mode 100644 evennia/web/website/templates/website/registration/password_reset_email.html create mode 100644 evennia/web/website/templates/website/registration/password_reset_form.html diff --git a/evennia/web/website/templates/website/registration/password_reset_complete.html b/evennia/web/website/templates/website/registration/password_reset_complete.html new file mode 100644 index 0000000000..697b4bc4ad --- /dev/null +++ b/evennia/web/website/templates/website/registration/password_reset_complete.html @@ -0,0 +1,31 @@ +{% extends "base.html" %} + +{% block titleblock %} +Forgot Password - Reset Successful +{% endblock %} + +{% block body %} + +{% load addclass %} +
+
+
+
+
+

Password Reset

+
+ {% if user.is_authenticated %} + + {% else %} + +

Your password has been successfully reset!

+ +

You may now log in using it here.

+ + {% endif %} +
+
+
+
+
+{% endblock %} diff --git a/evennia/web/website/templates/website/registration/password_reset_confirm.html b/evennia/web/website/templates/website/registration/password_reset_confirm.html new file mode 100644 index 0000000000..a7bdc683be --- /dev/null +++ b/evennia/web/website/templates/website/registration/password_reset_confirm.html @@ -0,0 +1,55 @@ +{% extends "base.html" %} + +{% block titleblock %} +Forgot Password - Reset +{% endblock %} + +{% block body %} + +{% load addclass %} +
+
+
+
+
+

Reset Password

+
+ {% if not validlink %} + + {% else %} + + {% if form.errors %} + {% for field in form %} + {% for error in field.errors %} + + {% endfor %} + {% endfor %} + {% endif %} + +
+ {% csrf_token %} + +
+ + {{ form.new_password1 | addclass:"form-control" }} +
+ +
+ + {{ form.new_password2 | addclass:"form-control" }} +
+ +
+
+ + +
+
+ + {% endif %} +
+
+
+
+
+{% endblock %} diff --git a/evennia/web/website/templates/website/registration/password_reset_done.html b/evennia/web/website/templates/website/registration/password_reset_done.html new file mode 100644 index 0000000000..d248c56d0f --- /dev/null +++ b/evennia/web/website/templates/website/registration/password_reset_done.html @@ -0,0 +1,34 @@ +{% extends "base.html" %} + +{% block titleblock %} +Forgot Password - Reset Link Sent +{% endblock %} + +{% block body %} + +{% load addclass %} +
+
+
+
+
+

Reset Sent

+
+ {% if user.is_authenticated %} + + {% else %} + +

Instructions for resetting your password will be emailed to the + address you provided, if that address matches the one we have on file + for your account. You should receive them shortly.

+ +

Please allow up to to a few hours for the email to transmit, and be + sure to check your spam folder if it doesn't show up in a timely manner.

+ + {% endif %} +
+
+
+
+
+{% endblock %} diff --git a/evennia/web/website/templates/website/registration/password_reset_email.html b/evennia/web/website/templates/website/registration/password_reset_email.html new file mode 100644 index 0000000000..28e5a0daa2 --- /dev/null +++ b/evennia/web/website/templates/website/registration/password_reset_email.html @@ -0,0 +1,15 @@ +{% autoescape off %} +To initiate the password reset process for your {{ user.get_username }} {{ site_name }} account, +click the link below: + +{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} + +If clicking the link above doesn't work, please copy and paste the URL in a new browser +window instead. + +If you did not request a password reset, please disregard this notice. Whoever requested it +cannot follow through on resetting your password without access to this message. + +Sincerely, +{{ site_name }} Management. +{% endautoescape %} \ No newline at end of file diff --git a/evennia/web/website/templates/website/registration/password_reset_form.html b/evennia/web/website/templates/website/registration/password_reset_form.html new file mode 100644 index 0000000000..f13c532a58 --- /dev/null +++ b/evennia/web/website/templates/website/registration/password_reset_form.html @@ -0,0 +1,48 @@ +{% extends "base.html" %} + +{% block titleblock %} +Forgot Password +{% endblock %} + +{% block body %} + +{% load addclass %} +
+
+
+
+
+

Forgot Password

+
+ {% if user.is_authenticated %} + + {% else %} + {% if form.errors %} + + {% endif %} + {% endif %} + + {% if not user.is_authenticated %} +
+ {% csrf_token %} + +
+ + {{ form.email | addclass:"form-control" }} + The email address you provided at registration. If you left it blank, your password cannot be reset through this form. +
+ +
+
+ + +
+
+ + {% endif %} +
+
+
+
+
+{% endblock %} From 0a00d3dc560e2693b18b0689aee35cde2c8f77f5 Mon Sep 17 00:00:00 2001 From: Griatch Date: Thu, 4 Oct 2018 21:46:16 +0200 Subject: [PATCH 067/493] Converting the AMP, not working yet --- evennia/server/portal/amp.py | 37 +++++++++++++++++++++----------- evennia/utils/idmapper/models.py | 3 ++- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/evennia/server/portal/amp.py b/evennia/server/portal/amp.py index c3882c22dd..a29ab6a3e1 100644 --- a/evennia/server/portal/amp.py +++ b/evennia/server/portal/amp.py @@ -9,7 +9,7 @@ from functools import wraps import time from twisted.protocols import amp from collections import defaultdict, namedtuple -from io import StringIO +from io import StringIO, BytesIO from itertools import count import zlib # Used in Compressed class import pickle @@ -41,8 +41,8 @@ SSHUTD = chr(17) # server shutdown PSTATUS = chr(18) # ping server or portal status SRESET = chr(19) # server shutdown in reset mode -NUL = b'\0' -NULNUL = '\0\0' +NUL = b'\x00' +NULNUL = b'\x00\x00' AMP_MAXLEN = amp.MAX_VALUE_LENGTH # max allowed data length in AMP protocol (cannot be changed) @@ -55,7 +55,7 @@ _MSGBUFFER = defaultdict(list) DUMMYSESSION = namedtuple('DummySession', ['sessid'])(0) -_HTTP_WARNING = """ +_HTTP_WARNING = bytes(""" HTTP/1.1 200 OK Content-Type: text/html @@ -67,7 +67,7 @@ Content-Type: text/html

This port should NOT be publicly visible.

-""".strip() +""".strip(), 'utf-8') # Helper functions for pickling. @@ -113,7 +113,8 @@ class Compressed(amp.String): put it back together here. """ - value = StringIO() + + value = BytesIO() value.write(self.fromStringProto(strings.get(name), proto)) for counter in count(2): # count from 2 upwards @@ -121,7 +122,7 @@ class Compressed(amp.String): if chunk is None: break value.write(self.fromStringProto(chunk, proto)) - objects[name] = value.getvalue() + objects[str(name, 'utf-8')] = value.getvalue() def toBox(self, name, strings, objects, proto): """ @@ -129,8 +130,14 @@ class Compressed(amp.String): we break up too-long data snippets into multiple batches here. """ - value = StringIO(objects[name]) + + # print("toBox: name={}, strings={}, objects={}, proto{}".format(name, strings, objects, proto)) + + value = BytesIO(objects[str(name, 'utf-8')]) strings[name] = self.toStringProto(value.read(AMP_MAXLEN), proto) + + # print("toBox strings[name] = {}".format(strings[name])) + for counter in count(2): chunk = value.read(AMP_MAXLEN) if not chunk: @@ -140,12 +147,16 @@ class Compressed(amp.String): def toString(self, inObject): """ Convert to send as a string on the wire, with compression. + + Note: In Py3 this is really a byte stream. + """ return zlib.compress(super(Compressed, self).toString(inObject), 9) def fromString(self, inString): """ Convert (decompress) from the string-representation on the wire to Python. + """ return super(Compressed, self).fromString(zlib.decompress(inString)) @@ -167,7 +178,7 @@ class MsgPortal2Server(amp.Command): Message Portal -> Server """ - key = "MsgPortal2Server" + key = b"MsgPortal2Server" arguments = [(b'packed_data', Compressed())] errors = {Exception: b'EXCEPTION'} response = [] @@ -271,7 +282,7 @@ class AMPMultiConnectionProtocol(amp.AMP): """ Handle non-AMP messages, such as HTTP communication. """ - if data[0] == NUL: + if data[:1] == NUL: # an AMP communication if data[-2:] != NULNUL: # an incomplete AMP box means more batches are forthcoming. @@ -287,7 +298,7 @@ class AMPMultiConnectionProtocol(amp.AMP): # not an AMP communication, return warning self.transport.write(_HTTP_WARNING) self.transport.loseConnection() - print("HTML received: %s" % data) + print("HTTP received: %s" % data) def makeConnection(self, transport): """ @@ -348,9 +359,9 @@ class AMPMultiConnectionProtocol(amp.AMP): Process incoming packed data. Args: - packed_data (bytes): Zip-packed data. + packed_data (bytes): Pickled data. Returns: - unpaced_data (any): Unpacked package + unpaced_data (any): Unpickled package """ return loads(packed_data) diff --git a/evennia/utils/idmapper/models.py b/evennia/utils/idmapper/models.py index e224143a10..54ca44d572 100644 --- a/evennia/utils/idmapper/models.py +++ b/evennia/utils/idmapper/models.py @@ -194,7 +194,8 @@ class SharedMemoryModelBase(ModelBase): # exclude some models that should not auto-create wrapper fields if cls.__name__ in ("ServerConfig", "TypeNick"): return - # dynamically create the wrapper properties for all fields not already handled (manytomanyfields are always handlers) + # dynamically create the wrapper properties for all fields not already handled + # (manytomanyfields are always handlers) for fieldname, field in ((fname, field) for fname, field in listitems(attrs) if fname.startswith("db_") and type(field).__name__ != "ManyToManyField"): foreignkey = type(field).__name__ == "ForeignKey" From f53fdbd681e8d8511b26a0ebe56277e7623576da Mon Sep 17 00:00:00 2001 From: Johnny Date: Thu, 4 Oct 2018 20:04:37 +0000 Subject: [PATCH 068/493] Enables django.contrib.messages via INSTALLED_APPS and adds a context processor for it. --- evennia/settings_default.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/evennia/settings_default.py b/evennia/settings_default.py index 9efbb6314b..4922b08996 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -751,6 +751,7 @@ TEMPLATES = [{ 'django.contrib.auth.context_processors.auth', 'django.template.context_processors.media', 'django.template.context_processors.debug', + 'django.contrib.messages.context_processors.messages', 'sekizai.context_processors.sekizai', 'evennia.web.utils.general_context.general_context'], # While true, show "pretty" error messages for template syntax errors. @@ -785,6 +786,7 @@ INSTALLED_APPS = ( 'django.contrib.flatpages', 'django.contrib.sites', 'django.contrib.staticfiles', + 'django.contrib.messages', 'sekizai', 'evennia.utils.idmapper', 'evennia.server', From 82a95195672301e49bde340011d63898e82e32b2 Mon Sep 17 00:00:00 2001 From: Johnny Date: Thu, 4 Oct 2018 20:05:32 +0000 Subject: [PATCH 069/493] Adds hook to retrieve messsages and an include for the actual blocks. --- evennia/web/website/templates/website/base.html | 2 ++ evennia/web/website/templates/website/messages.html | 9 +++++++++ 2 files changed, 11 insertions(+) create mode 100644 evennia/web/website/templates/website/messages.html diff --git a/evennia/web/website/templates/website/base.html b/evennia/web/website/templates/website/base.html index 2004425849..e690d61d2b 100644 --- a/evennia/web/website/templates/website/base.html +++ b/evennia/web/website/templates/website/base.html @@ -42,6 +42,8 @@
{% endif %} diff --git a/evennia/web/website/templates/website/messages.html b/evennia/web/website/templates/website/messages.html new file mode 100644 index 0000000000..7b237180eb --- /dev/null +++ b/evennia/web/website/templates/website/messages.html @@ -0,0 +1,9 @@ +{% if messages %} + +{% for message in messages %} +
+ {{ message }} +
+{% endfor %} + +{% endif %} \ No newline at end of file From 95cf405ab4f78892526b24a3d41d3dfe93ad6b93 Mon Sep 17 00:00:00 2001 From: Johnny Date: Thu, 4 Oct 2018 20:05:55 +0000 Subject: [PATCH 070/493] Adds include block for messages. --- evennia/web/website/templates/website/registration/login.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/evennia/web/website/templates/website/registration/login.html b/evennia/web/website/templates/website/registration/login.html index 87f5d7b9f7..27339c9480 100644 --- a/evennia/web/website/templates/website/registration/login.html +++ b/evennia/web/website/templates/website/registration/login.html @@ -14,6 +14,7 @@ Login

Login


+ {% include 'website/messages.html' %} {% if user.is_authenticated %} {% else %} @@ -39,7 +40,7 @@ Login

From cc26e12e9ff90702ec0f99bf4a3f72ec66c9471a Mon Sep 17 00:00:00 2001 From: Johnny Date: Thu, 4 Oct 2018 20:21:02 +0000 Subject: [PATCH 071/493] Adds account registration form. --- evennia/web/website/forms.py | 13 +++++ .../web/website/templates/website/_menu.html | 2 +- .../templates/website/registration/login.html | 2 +- .../website/registration/register.html | 56 +++++++++++++++++++ evennia/web/website/urls.py | 3 +- evennia/web/website/views.py | 42 ++++++++++++++ 6 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 evennia/web/website/forms.py create mode 100644 evennia/web/website/templates/website/registration/register.html diff --git a/evennia/web/website/forms.py b/evennia/web/website/forms.py new file mode 100644 index 0000000000..cc7677926c --- /dev/null +++ b/evennia/web/website/forms.py @@ -0,0 +1,13 @@ +from django import forms +from django.conf import settings +from django.contrib.auth.forms import UserCreationForm, UsernameField +from evennia.utils import class_from_module + +class AccountCreationForm(UserCreationForm): + + class Meta: + model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS) + fields = ("username", "email") + field_classes = {'username': UsernameField} + + email = forms.EmailField(help_text="A valid email address. Optional; used for password resets.", required=False) \ No newline at end of file diff --git a/evennia/web/website/templates/website/_menu.html b/evennia/web/website/templates/website/_menu.html index 8430d0f805..6a81d04866 100644 --- a/evennia/web/website/templates/website/_menu.html +++ b/evennia/web/website/templates/website/_menu.html @@ -51,7 +51,7 @@ folder and edit it to add/remove links to the menu. Log In
  • - Register + Register
  • {% endif %} {% endblock %} diff --git a/evennia/web/website/templates/website/registration/login.html b/evennia/web/website/templates/website/registration/login.html index 27339c9480..cf798e8f76 100644 --- a/evennia/web/website/templates/website/registration/login.html +++ b/evennia/web/website/templates/website/registration/login.html @@ -40,7 +40,7 @@ Login

    diff --git a/evennia/web/website/templates/website/registration/register.html b/evennia/web/website/templates/website/registration/register.html new file mode 100644 index 0000000000..5475d922be --- /dev/null +++ b/evennia/web/website/templates/website/registration/register.html @@ -0,0 +1,56 @@ +{% extends "base.html" %} + +{% block titleblock %} +Register +{% endblock %} + +{% block body %} + +{% load addclass %} +
    +
    +
    +
    +
    +

    Register

    +
    + {% if user.is_authenticated %} + + {% else %} + {% if form.errors %} + {% for field in form %} + {% for error in field.errors %} + + {% endfor %} + {% endfor %} + {% endif %} + {% endif %} + + {% if not user.is_authenticated %} +
    + {% csrf_token %} + + {% for field in form %} +
    + {{ field.label_tag }} + {{ field | addclass:"form-control" }} + {% if field.help_text %} + {{ field.help_text|safe }} + {% endif %} +
    + {% endfor %} + +
    +
    + + +
    +
    + + {% endif %} +
    +
    +
    +
    +
    +{% endblock %} diff --git a/evennia/web/website/urls.py b/evennia/web/website/urls.py index f906b6b142..1cc7fe75d8 100644 --- a/evennia/web/website/urls.py +++ b/evennia/web/website/urls.py @@ -13,7 +13,8 @@ urlpatterns = [ url(r'^tbi/', website_views.to_be_implemented, name='to_be_implemented'), # User Authentication (makes login/logout url names available) - url(r'^authenticate/', include('django.contrib.auth.urls')), + url(r'^auth/', include('django.contrib.auth.urls')), + url(r'^auth/register', website_views.AccountCreationView.as_view(), name="register"), # Django original admin page. Make this URL is always available, whether # we've chosen to use Evennia's custom admin or not. diff --git a/evennia/web/website/views.py b/evennia/web/website/views.py index fe93b06426..e4342ebc6d 100644 --- a/evennia/web/website/views.py +++ b/evennia/web/website/views.py @@ -7,14 +7,19 @@ templates on the fly. """ from django.contrib.admin.sites import site from django.conf import settings +from django.contrib import messages from django.contrib.auth import authenticate from django.contrib.admin.views.decorators import staff_member_required +from django.http import HttpResponseRedirect from django.shortcuts import render +from django.urls import reverse, reverse_lazy +from django.views.generic import View, DetailView, ListView, FormView from evennia import SESSION_HANDLER from evennia.objects.models import ObjectDB from evennia.accounts.models import AccountDB from evennia.utils import logger +from evennia.web.website.forms import AccountCreationForm from django.contrib.auth import login @@ -134,3 +139,40 @@ def admin_wrapper(request): Wrapper that allows us to properly use the base Django admin site, if needed. """ return staff_member_required(site.index)(request) + +class AccountCreationView(FormView): + form_class = AccountCreationForm + template_name = 'website/registration/register.html' + success_url = reverse_lazy('login') + + def form_valid(self, form): + # Check to make sure basics validated + valid = super(AccountCreationView, self).form_valid(form) + if not valid: return self.form_invalid(form) + + username = form.cleaned_data['username'] + password = form.cleaned_data['password1'] + email = form.cleaned_data.get('email', '') + + # Create a fake session object to intercept calls to the terminal + from mock import Mock + session = self.request + session.address = self.request.META.get('REMOTE_ADDR', '') + session.msg = Mock() + + # Create account + from evennia.commands.default.unloggedin import _create_account + permissions = settings.PERMISSION_ACCOUNT_DEFAULT + account = _create_account(session, username, password, permissions) + + # If unsuccessful, get messages passed to session.msg + if not account: + [messages.error(self.request, call) for call in session.msg.call_args_list] + return self.form_invalid(form) + + # Append email address if given + account.email = email + account.save() + + messages.success(self.request, "Your account '%s' was successfully created! You may log in using it now." % account.name) + return HttpResponseRedirect(self.success_url) \ No newline at end of file From 5031e06a603d45a5bdddf772c75e0afbcf7b2fcf Mon Sep 17 00:00:00 2001 From: Griatch Date: Thu, 4 Oct 2018 22:36:25 +0200 Subject: [PATCH 072/493] Still not responding --- evennia/VERSION.txt | 2 +- evennia/server/amp.py | 667 ---------------------------- evennia/server/amp_client.py | 1 + evennia/server/evennia_launcher.py | 7 +- evennia/server/portal/amp.py | 4 +- evennia/server/portal/amp_server.py | 2 + 6 files changed, 13 insertions(+), 670 deletions(-) delete mode 100644 evennia/server/amp.py diff --git a/evennia/VERSION.txt b/evennia/VERSION.txt index a3df0a6959..c70836ca5c 100644 --- a/evennia/VERSION.txt +++ b/evennia/VERSION.txt @@ -1 +1 @@ -0.8.0 +0.9.0-dev diff --git a/evennia/server/amp.py b/evennia/server/amp.py deleted file mode 100644 index 0f9fec6a46..0000000000 --- a/evennia/server/amp.py +++ /dev/null @@ -1,667 +0,0 @@ -""" -Contains the protocols, commands, and client factory needed for the Server -and Portal to communicate with each other, letting Portal work as a proxy. -Both sides use this same protocol. - -The separation works like this: - -Portal - (AMP client) handles protocols. It contains a list of connected - sessions in a dictionary for identifying the respective account - connected. If it loses the AMP connection it will automatically - try to reconnect. - -Server - (AMP server) Handles all mud operations. The server holds its own list - of sessions tied to account objects. This is synced against the portal - at startup and when a session connects/disconnects - -""" - - -# imports needed on both server and portal side -import os -import time -from collections import defaultdict, namedtuple -from itertools import count -from io import BytesIO -import pickle -from twisted.protocols import amp -from twisted.internet import protocol -from twisted.internet.defer import Deferred -from evennia.utils import logger -from evennia.utils.utils import to_str, variable_from_module -import zlib # Used in Compressed class - -DUMMYSESSION = namedtuple('DummySession', ['sessid'])(0) - -# communication bits -# (chr(9) and chr(10) are \t and \n, so skipping them) - -PCONN = b'\x01' # portal session connect -PDISCONN = b'\x02' # portal session disconnect -PSYNC = b'\x03' # portal session sync -SLOGIN = b'\x04' # server session login -SDISCONN = b'\x05' # server session disconnect -SDISCONNALL = b'\x06' # server session disconnect all -SSHUTD = b'\x07' # server shutdown -SSYNC = b'\x08' # server session sync -SCONN = b'\x0b' # server creating new connection (for irc bots and etc) -PCONNSYNC = b'\x0c' # portal post-syncing a session -PDISCONNALL = b'\x0d' # portal session disconnect all -AMP_MAXLEN = amp.MAX_VALUE_LENGTH # max allowed data length in AMP protocol (cannot be changed) - -BATCH_RATE = 250 # max commands/sec before switching to batch-sending -BATCH_TIMEOUT = 0.5 # how often to poll to empty batch queue, in seconds - -# buffers -_SENDBATCH = defaultdict(list) -_MSGBUFFER = defaultdict(list) - - -def get_restart_mode(restart_file): - """ - Parse the server/portal restart status - - Args: - restart_file (str): Path to restart.dat file. - - Returns: - restart_mode (bool): If the file indicates the server is in - restart mode or not. - - """ - if os.path.exists(restart_file): - flag = open(restart_file, 'r').read() - return flag == "True" - return False - - -class AmpServerFactory(protocol.ServerFactory): - """ - This factory creates the Server as a new AMPProtocol instance for accepting - connections from the Portal. - """ - noisy = False - - def __init__(self, server): - """ - Initialize the factory. - - Args: - server (Server): The Evennia server service instance. - protocol (Protocol): The protocol the factory creates - instances of. - - """ - self.server = server - self.protocol = AMPProtocol - - def buildProtocol(self, addr): - """ - Start a new connection, and store it on the service object. - - Args: - addr (str): Connection address. Not used. - - Returns: - protocol (Protocol): The created protocol. - - """ - self.server.amp_protocol = AMPProtocol() - self.server.amp_protocol.factory = self - return self.server.amp_protocol - - -class AmpClientFactory(protocol.ReconnectingClientFactory): - """ - This factory creates an instance of the Portal, an AMPProtocol - instances to use to connect - - """ - # Initial reconnect delay in seconds. - initialDelay = 1 - factor = 1.5 - maxDelay = 1 - noisy = False - - def __init__(self, portal): - """ - Initializes the client factory. - - Args: - portal (Portal): Portal instance. - - """ - self.portal = portal - self.protocol = AMPProtocol - - def startedConnecting(self, connector): - """ - Called when starting to try to connect to the MUD server. - - Args: - connector (Connector): Twisted Connector instance representing - this connection. - - """ - pass - - def buildProtocol(self, addr): - """ - Creates an AMPProtocol instance when connecting to the server. - - Args: - addr (str): Connection address. Not used. - - """ - self.resetDelay() - self.portal.amp_protocol = AMPProtocol() - self.portal.amp_protocol.factory = self - return self.portal.amp_protocol - - def clientConnectionLost(self, connector, reason): - """ - Called when the AMP connection to the MUD server is lost. - - Args: - connector (Connector): Twisted Connector instance representing - this connection. - reason (str): Eventual text describing why connection was lost. - - """ - if hasattr(self, "server_restart_mode"): - self.portal.sessions.announce_all(" Server restarting ...") - self.maxDelay = 2 - else: - # Don't translate this; avoid loading django on portal side. - self.maxDelay = 10 - self.portal.sessions.announce_all(" ... Portal lost connection to Server.") - protocol.ReconnectingClientFactory.clientConnectionLost(self, connector, reason) - - def clientConnectionFailed(self, connector, reason): - """ - Called when an AMP connection attempt to the MUD server fails. - - Args: - connector (Connector): Twisted Connector instance representing - this connection. - reason (str): Eventual text describing why connection failed. - - """ - if hasattr(self, "server_restart_mode"): - self.maxDelay = 2 - else: - self.maxDelay = 10 - self.portal.sessions.announce_all(" ...") - protocol.ReconnectingClientFactory.clientConnectionFailed(self, connector, reason) - - -# AMP Communication Command types - -class Compressed(amp.String): - """ - This is a customn AMP command Argument that both handles too-long - sends as well as uses zlib for compression across the wire. The - batch-grouping of too-long sends is borrowed from the "mediumbox" - recipy at twisted-hacks's ~glyph/+junk/amphacks/mediumbox. - - """ - - def fromBox(self, name, strings, objects, proto): - """ - Converts from box representation to python. We - group very long data into batches. - """ - value = BytesIO() - value.write(strings.get(name)) - for counter in count(2): - # count from 2 upwards - chunk = strings.get(b"%s.%d" % (name, counter)) - if chunk is None: - break - value.write(chunk) - objects[name.decode()] = value.getvalue() - - def toBox(self, name, strings, objects, proto): - """ - Convert from data to box. We handled too-long - batched data and put it together here. - """ - value = BytesIO(objects[name.decode()]) - strings[name] = value.read(AMP_MAXLEN) - for counter in count(2): - chunk = value.read(AMP_MAXLEN) - if not chunk: - break - strings[b"%s.%d" % (name, counter)] = chunk - - def toString(self, inObject): - """ - Convert to send on the wire, with compression. - """ - return zlib.compress(inObject, 9) - - def fromString(self, inString): - """ - Convert (decompress) from the wire to Python. - """ - return zlib.decompress(inString) - - -class MsgPortal2Server(amp.Command): - """ - Message Portal -> Server - - """ - key = "MsgPortal2Server" - arguments = [(b'packed_data', Compressed())] - errors = {Exception: b'EXCEPTION'} - response = [] - - -class MsgServer2Portal(amp.Command): - """ - Message Server -> Portal - - """ - key = "MsgServer2Portal" - arguments = [(b'packed_data', Compressed())] - errors = {Exception: b'EXCEPTION'} - response = [] - - -class AdminPortal2Server(amp.Command): - """ - Administration Portal -> Server - - Sent when the portal needs to perform admin operations on the - server, such as when a new session connects or resyncs - - """ - key = "AdminPortal2Server" - arguments = [(b'packed_data', Compressed())] - errors = {Exception: b'EXCEPTION'} - response = [] - - -class AdminServer2Portal(amp.Command): - """ - Administration Server -> Portal - - Sent when the server needs to perform admin operations on the - portal. - - """ - key = "AdminServer2Portal" - arguments = [(b'packed_data', Compressed())] - errors = {Exception: b'EXCEPTION'} - response = [] - - -class FunctionCall(amp.Command): - """ - Bidirectional Server <-> Portal - - Sent when either process needs to call an arbitrary function in - the other. This does not use the batch-send functionality. - - """ - key = "FunctionCall" - arguments = [(b'module', amp.String()), - (b'function', amp.String()), - (b'args', amp.String()), - (b'kwargs', amp.String())] - errors = {Exception: b'EXCEPTION'} - response = [(b'result', amp.String())] - - -# Helper functions for pickling. - -def dumps(data): - return pickle.dumps(data, pickle.HIGHEST_PROTOCOL) - - -def loads(data): - return pickle.loads(data) - - -# ------------------------------------------------------------- -# Core AMP protocol for communication Server <-> Portal -# ------------------------------------------------------------- - -class AMPProtocol(amp.AMP): - """ - This is the protocol that the MUD server and the proxy server - communicate to each other with. AMP is a bi-directional protocol, - so both the proxy and the MUD use the same commands and protocol. - - AMP specifies responder methods here and connect them to - amp.Command subclasses that specify the datatypes of the - input/output of these methods. - - """ - - # helper methods - - def __init__(self, *args, **kwargs): - """ - Initialize protocol with some things that need to be in place - already before connecting both on portal and server. - - """ - self.send_batch_counter = 0 - self.send_reset_time = time.time() - self.send_mode = True - self.send_task = None - - def connectionMade(self): - """ - This is called when an AMP connection is (re-)established - between server and portal. AMP calls it on both sides, so we - need to make sure to only trigger resync from the portal side. - - """ - # this makes for a factor x10 faster sends across the wire - self.transport.setTcpNoDelay(True) - - if hasattr(self.factory, "portal"): - # only the portal has the 'portal' property, so we know we are - # on the portal side and can initialize the connection. - sessdata = self.factory.portal.sessions.get_all_sync_data() - self.send_AdminPortal2Server(DUMMYSESSION, - PSYNC, - sessiondata=sessdata) - self.factory.portal.sessions.at_server_connection() - if hasattr(self.factory, "server_restart_mode"): - del self.factory.server_restart_mode - - def connectionLost(self, reason): - """ - We swallow connection errors here. The reason is that during a - normal reload/shutdown there will almost always be cases where - either the portal or server shuts down before a message has - returned its (empty) return, triggering a connectionLost error - that is irrelevant. If a true connection error happens, the - portal will continuously try to reconnect, showing the problem - that way. - """ - pass - - # Error handling - - def errback(self, e, info): - """ - Error callback. - Handles errors to avoid dropping connections on server tracebacks. - - Args: - e (Failure): Deferred error instance. - info (str): Error string. - - """ - e.trap(Exception) - logger.log_err("AMP Error for %(info)s: %(e)s" % {'info': info, - 'e': e.getErrorMessage()}) - - def send_data(self, command, sessid, **kwargs): - """ - Send data across the wire. - - Args: - command (AMP Command): A protocol send command. - sessid (int): A unique Session id. - - Returns: - deferred (deferred or None): A deferred with an errback. - - Notes: - Data will be sent across the wire pickled as a tuple - (sessid, kwargs). - - """ - return self.callRemote(command, - packed_data=dumps((sessid, kwargs)) - ).addErrback(self.errback, command.key) - - # Message definition + helper methods to call/create each message type - - # Portal -> Server Msg - - @MsgPortal2Server.responder - def server_receive_msgportal2server(self, packed_data): - """ - Receives message arriving to server. This method is executed - on the Server. - - Args: - packed_data (str): Data to receive (a pickled tuple (sessid,kwargs)) - - """ - sessid, kwargs = loads(packed_data) - session = self.factory.server.sessions.get(sessid, None) - if session: - self.factory.server.sessions.data_in(session, **kwargs) - return {} - - def send_MsgPortal2Server(self, session, **kwargs): - """ - Access method called by the Portal and executed on the Portal. - - Args: - session (session): Session - kwargs (any, optional): Optional data. - - Returns: - deferred (Deferred): Asynchronous return. - - """ - return self.send_data(MsgPortal2Server, session.sessid, **kwargs) - - # Server -> Portal message - - @MsgServer2Portal.responder - def portal_receive_server2portal(self, packed_data): - """ - Receives message arriving to Portal from Server. - This method is executed on the Portal. - - Args: - packed_data (str): Pickled data (sessid, kwargs) coming over the wire. - """ - sessid, kwargs = loads(packed_data) - session = self.factory.portal.sessions.get(sessid, None) - if session: - self.factory.portal.sessions.data_out(session, **kwargs) - return {} - - def send_MsgServer2Portal(self, session, **kwargs): - """ - Access method - executed on the Server for sending data - to Portal. - - Args: - session (Session): Unique Session. - kwargs (any, optional): Extra data. - - """ - return self.send_data(MsgServer2Portal, session.sessid, **kwargs) - - # Server administration from the Portal side - @AdminPortal2Server.responder - def server_receive_adminportal2server(self, packed_data): - """ - Receives admin data from the Portal (allows the portal to - perform admin operations on the server). This is executed on - the Server. - - Args: - packed_data (str): Incoming, pickled data. - - """ - sessid, kwargs = loads(packed_data) - operation = kwargs.pop("operation", "") - server_sessionhandler = self.factory.server.sessions - - if operation == PCONN: # portal_session_connect - # create a new session and sync it - server_sessionhandler.portal_connect(kwargs.get("sessiondata")) - - elif operation == PCONNSYNC: # portal_session_sync - server_sessionhandler.portal_session_sync(kwargs.get("sessiondata")) - - elif operation == PDISCONN: # portal_session_disconnect - # session closed from portal sid - session = server_sessionhandler.get(sessid) - if session: - server_sessionhandler.portal_disconnect(session) - - elif operation == PDISCONNALL: # portal_disconnect_all - # portal orders all sessions to close - server_sessionhandler.portal_disconnect_all() - - elif operation == PSYNC: # portal_session_sync - # force a resync of sessions when portal reconnects to - # server (e.g. after a server reboot) the data kwarg - # contains a dict {sessid: {arg1:val1,...}} - # representing the attributes to sync for each - # session. - server_sessionhandler.portal_sessions_sync(kwargs.get("sessiondata")) - else: - raise Exception("operation %(op)s not recognized." % {'op': operation}) - return {} - - def send_AdminPortal2Server(self, session, operation="", **kwargs): - """ - Send Admin instructions from the Portal to the Server. - Executed - on the Portal. - - Args: - session (Session): Session. - operation (char, optional): Identifier for the server operation, as defined by the - global variables in `evennia/server/amp.py`. - data (str or dict, optional): Data used in the administrative operation. - - """ - return self.send_data(AdminPortal2Server, session.sessid, operation=operation, **kwargs) - - # Portal administration from the Server side - - @AdminServer2Portal.responder - def portal_receive_adminserver2portal(self, packed_data): - """ - - Receives and handles admin operations sent to the Portal - This is executed on the Portal. - - Args: - packed_data (str): Data received, a pickled tuple (sessid, kwargs). - - """ - sessid, kwargs = loads(packed_data) - operation = kwargs.pop("operation") - portal_sessionhandler = self.factory.portal.sessions - - if operation == SLOGIN: # server_session_login - # a session has authenticated; sync it. - session = portal_sessionhandler.get(sessid) - if session: - portal_sessionhandler.server_logged_in(session, kwargs.get("sessiondata")) - - elif operation == SDISCONN: # server_session_disconnect - # the server is ordering to disconnect the session - session = portal_sessionhandler.get(sessid) - if session: - portal_sessionhandler.server_disconnect(session, reason=kwargs.get("reason")) - - elif operation == SDISCONNALL: # server_session_disconnect_all - # server orders all sessions to disconnect - portal_sessionhandler.server_disconnect_all(reason=kwargs.get("reason")) - - elif operation == SSHUTD: # server_shutdown - # the server orders the portal to shut down - self.factory.portal.shutdown(restart=False) - - elif operation == SSYNC: # server_session_sync - # server wants to save session data to the portal, - # maybe because it's about to shut down. - portal_sessionhandler.server_session_sync(kwargs.get("sessiondata"), - kwargs.get("clean", True)) - # set a flag in case we are about to shut down soon - self.factory.server_restart_mode = True - - elif operation == SCONN: # server_force_connection (for irc/etc) - portal_sessionhandler.server_connect(**kwargs) - - else: - raise Exception("operation %(op)s not recognized." % {'op': operation}) - return {} - - def send_AdminServer2Portal(self, session, operation="", **kwargs): - """ - Administrative access method called by the Server to send an - instruction to the Portal. - - Args: - session (Session): Session. - operation (char, optional): Identifier for the server - operation, as defined by the global variables in - `evennia/server/amp.py`. - data (str or dict, optional): Data going into the adminstrative. - - """ - return self.send_data(AdminServer2Portal, session.sessid, operation=operation, **kwargs) - - # Extra functions - - @FunctionCall.responder - def receive_functioncall(self, module, function, func_args, func_kwargs): - """ - This allows Portal- and Server-process to call an arbitrary - function in the other process. It is intended for use by - plugin modules. - - Args: - module (str or module): The module containing the - `function` to call. - function (str): The name of the function to call in - `module`. - func_args (str): Pickled args tuple for use in `function` call. - func_kwargs (str): Pickled kwargs dict for use in `function` call. - - """ - args = loads(func_args) - kwargs = loads(func_kwargs) - - # call the function (don't catch tracebacks here) - result = variable_from_module(module, function)(*args, **kwargs) - - if isinstance(result, Deferred): - # if result is a deferred, attach handler to properly - # wrap the return value - result.addCallback(lambda r: {"result": dumps(r)}) - return result - else: - return {'result': dumps(result)} - - def send_FunctionCall(self, modulepath, functionname, *args, **kwargs): - """ - Access method called by either process. This will call an arbitrary - function on the other process (On Portal if calling from Server and - vice versa). - - Inputs: - modulepath (str) - python path to module holding function to call - functionname (str) - name of function in given module - *args, **kwargs will be used as arguments/keyword args for the - remote function call - Returns: - A deferred that fires with the return value of the remote - function call - - """ - return self.callRemote(FunctionCall, - module=modulepath, - function=functionname, - args=dumps(args), - kwargs=dumps(kwargs)).addCallback( - lambda r: loads(r["result"])).addErrback(self.errback, "FunctionCall") diff --git a/evennia/server/amp_client.py b/evennia/server/amp_client.py index a4300adf4d..e6a003975f 100644 --- a/evennia/server/amp_client.py +++ b/evennia/server/amp_client.py @@ -128,6 +128,7 @@ class AMPServerClientProtocol(amp.AMPMultiConnectionProtocol): (sessid, kwargs). """ + print("server data_to_portal: {}, {}, {}".format(command, sessid, kwargs)) return self.callRemote(command, packed_data=amp.dumps((sessid, kwargs))).addErrback( self.errback, command.key) diff --git a/evennia/server/evennia_launcher.py b/evennia/server/evennia_launcher.py index fb711b3cef..86b10e47be 100644 --- a/evennia/server/evennia_launcher.py +++ b/evennia/server/evennia_launcher.py @@ -87,7 +87,7 @@ PSTATUS = chr(18) # ping server or portal status SRESET = chr(19) # shutdown server in reset mode # requirements -PYTHON_MIN = '2.7' +PYTHON_MIN = '3.5' TWISTED_MIN = '18.0.0' DJANGO_MIN = '1.11' DJANGO_REC = '1.11' @@ -632,6 +632,9 @@ def send_instruction(operation, arguments, callback=None, errback=None): """ global AMP_CONNECTION, REACTOR_RUN + print("send_instruction: {}, {}, {}, {}, {})".format(operation, arguments, callback, errback, AMP_CONNECTION)) + + if None in (AMP_HOST, AMP_PORT, AMP_INTERFACE): print(ERROR_AMP_UNCONFIGURED) sys.exit() @@ -660,8 +663,10 @@ def send_instruction(operation, arguments, callback=None, errback=None): def _send(): if operation == PSTATUS: + print("send PSTATUS ... {}".format(AMP_CONNECTION)) return AMP_CONNECTION.callRemote(MsgStatus, status="").addCallbacks(_callback, _errback) else: + print("send callremote") return AMP_CONNECTION.callRemote( MsgLauncher2Portal, operation=operation, diff --git a/evennia/server/portal/amp.py b/evennia/server/portal/amp.py index a29ab6a3e1..24afad49d1 100644 --- a/evennia/server/portal/amp.py +++ b/evennia/server/portal/amp.py @@ -364,7 +364,9 @@ class AMPMultiConnectionProtocol(amp.AMP): unpaced_data (any): Unpickled package """ - return loads(packed_data) + msg = loads(packed_data) + print("amp.data_in: {}".format(msg)) + return msg def broadcast(self, command, sessid, **kwargs): """ diff --git a/evennia/server/portal/amp_server.py b/evennia/server/portal/amp_server.py index c07b5c121d..454aad8558 100644 --- a/evennia/server/portal/amp_server.py +++ b/evennia/server/portal/amp_server.py @@ -136,6 +136,7 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol): (sessid, kwargs). """ + print("portal data_to_server: {}, {}, {}".format(command, sessid, kwargs)) if self.factory.server_connection: return self.factory.server_connection.callRemote( command, packed_data=amp.dumps((sessid, kwargs))).addErrback( @@ -275,6 +276,7 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol): (portal_running, server_running, portal_pid, server_pid). """ + print('Received PSTATUS request') return {"status": amp.dumps(self.get_status())} @amp.MsgLauncher2Portal.responder From 2d9cbb9a20cfe225fabdbe77e7b5ef91dc970eb9 Mon Sep 17 00:00:00 2001 From: Johnny Date: Thu, 4 Oct 2018 22:54:35 +0000 Subject: [PATCH 073/493] Adds authenticated dropdown with links to password change form, create/manage characters, and character quickselect. --- evennia/accounts/accounts.py | 4 ++++ .../web/website/templates/website/_menu.html | 17 +++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/evennia/accounts/accounts.py b/evennia/accounts/accounts.py index 2c33e5c1f8..f4711299cf 100644 --- a/evennia/accounts/accounts.py +++ b/evennia/accounts/accounts.py @@ -189,6 +189,10 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)): @lazy_property def sessions(self): return AccountSessionHandler(self) + + @lazy_property + def characters(self): + return self.db._playable_characters # session-related methods diff --git a/evennia/web/website/templates/website/_menu.html b/evennia/web/website/templates/website/_menu.html index 6a81d04866..ea01404967 100644 --- a/evennia/web/website/templates/website/_menu.html +++ b/evennia/web/website/templates/website/_menu.html @@ -40,8 +40,21 @@ folder and edit it to add/remove links to the menu. {% endblock %} {% block navbar_user %} {% if user.is_authenticated %} -
  • Log Out From 2a8799acbb8e8ee5651718f18280cffa894f7413 Mon Sep 17 00:00:00 2001 From: Johnny Date: Thu, 4 Oct 2018 22:54:48 +0000 Subject: [PATCH 074/493] Stylizes password_change form. --- .../registration/password_change_form.html | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 evennia/web/website/templates/website/registration/password_change_form.html diff --git a/evennia/web/website/templates/website/registration/password_change_form.html b/evennia/web/website/templates/website/registration/password_change_form.html new file mode 100644 index 0000000000..bae8c90962 --- /dev/null +++ b/evennia/web/website/templates/website/registration/password_change_form.html @@ -0,0 +1,51 @@ +{% extends "base.html" %} + +{% block titleblock %} +Password Change +{% endblock %} + +{% block content %} + +{% load addclass %} +
    +
    +
    +
    +
    +

    Password Change

    +
    + + {% if form.errors %} + {% for field in form %} + {% for error in field.errors %} + + {% endfor %} + {% endfor %} + {% endif %} + +
    + {% csrf_token %} + + {% for field in form %} +
    + {{ field.label_tag }} + {{ field | addclass:"form-control" }} + {% if field.help_text %} + {{ field.help_text|safe }} + {% endif %} +
    + {% endfor %} + +
    +
    + + +
    +
    + +
    +
    +
    +
    +
    +{% endblock %} From 3c0f02d66dd21f12194e05d193ecee3403c0b7b1 Mon Sep 17 00:00:00 2001 From: Johnny Date: Fri, 5 Oct 2018 18:59:55 +0000 Subject: [PATCH 075/493] Implements web-based character creation. --- evennia/web/website/forms.py | 131 +++++++++++++++++- .../web/website/templates/website/_menu.html | 2 +- .../templates/website/chargen_form.html | 51 +++++++ evennia/web/website/urls.py | 3 + evennia/web/website/views.py | 61 +++++++- 5 files changed, 244 insertions(+), 4 deletions(-) create mode 100644 evennia/web/website/templates/website/chargen_form.html diff --git a/evennia/web/website/forms.py b/evennia/web/website/forms.py index cc7677926c..ca93dd2c3b 100644 --- a/evennia/web/website/forms.py +++ b/evennia/web/website/forms.py @@ -2,6 +2,7 @@ from django import forms from django.conf import settings from django.contrib.auth.forms import UserCreationForm, UsernameField from evennia.utils import class_from_module +from random import choice, randint class AccountCreationForm(UserCreationForm): @@ -10,4 +11,132 @@ class AccountCreationForm(UserCreationForm): fields = ("username", "email") field_classes = {'username': UsernameField} - email = forms.EmailField(help_text="A valid email address. Optional; used for password resets.", required=False) \ No newline at end of file + email = forms.EmailField(help_text="A valid email address. Optional; used for password resets.", required=False) + +class CharacterCreationForm(forms.Form): + name = forms.CharField(help_text="The name of your intended character.") + age = forms.IntegerField(min_value=3, max_value=99, help_text="How old your character should be once spawned.") + description = forms.CharField(widget=forms.Textarea(attrs={'rows': 3}), max_length=2048, min_length=160, required=False) + + @classmethod + def assign_attributes(cls, attribute_list, points, min_points, max_points): + """ + Randomly distributes a number of points across the given attributes, + while also ensuring each attribute gets at least a certain amount + and at most a certain amount. + + Args: + attribute_list (iterable): List or tuple of attribute names to assign + points to. + points (int): Starting number of points + min_points (int): Least amount of points each attribute should have + max_points (int): Most amount of points each attribute should have + + Returns: + spread (dict): Dict of attributes and a point assignment. + + """ + num_buckets = len(attribute_list) + point_spread = (x for x in self.random_distribution(points, num_buckets, min_points, max_points)) + + # For each field, get the point calculation for the next attribute value generated + return {attribute: next(point_spread) for k in attribute_list} + + @classmethod + def random_distribution(cls, points, num_buckets, min_points, max_points): + """ + Distributes a set number of points randomly across a number of 'buckets' + while also attempting to ensure each bucket's value finishes within a + certain range. + + If your math doesn't add up (you try to distribute 5 points across 100 + buckets and insist each bucket has at least 20 points), the algorithm + will return the best spread it could achieve but will not raise an error + (so in this case, 5 random buckets would get 1 point each and that's all). + + Args: + points (int): The number of points to distribute. + num_buckets (int): The number of 'buckets' (or stats, skills, etc) + you wish to distribute points to. + min_points (int): The least amount of points each bucket should have. + max_points (int): The most points each bucket should have. + + Returns: + buckets (list): List of random point assignments. + + """ + buckets = [0 for x in range(num_buckets)] + indices = [i for (i, value) in enumerate(buckets)] + + # Do this while we have eligible buckets, points to assign and we haven't + # maxed out all the buckets. + while indices and points and sum(buckets) <= (max_points * num_buckets): + # Pick a random bucket index + index = choice(indices) + + # Add to bucket + buckets[index] = buckets[index] + 1 + points = points - 1 + + # Get the indices of eligible buckets + indices = [i for (i, value) in enumerate(buckets) if (value < min_points) or (value < max_points)] + + return buckets + +class ExtendedCharacterCreationForm(forms.Form): + + GENDERS = ( + ('male', 'Male'), + ('female', 'Female'), + ('androgynous', 'Androgynous'), + ('special', 'Special') + ) + + RACES = ( + ('human', 'Human'), + ('elf', 'Elf'), + ('orc', 'Orc'), + ) + + CLASSES = ( + ('civilian', 'Civilian'), + ('warrior', 'Warrior'), + ('thief', 'Thief'), + ('cleric', 'Cleric') + ) + + PERKS = ( + ('strong', 'Extra strength'), + ('nimble', 'Quick on their toes'), + ('diplomatic', 'Fast talker') + ) + + name = forms.CharField(help_text="The name of your intended character.") + age = forms.IntegerField(min_value=3, max_value=99, help_text="How old your character should be once spawned.") + gender = forms.ChoiceField(choices=GENDERS, help_text="Which end of the multidimensional spectrum does your character most closely align with, in terms of gender?") + race = forms.ChoiceField(choices=RACES, help_text="What race does your character belong to?") + job = forms.ChoiceField(choices=CLASSES, help_text="What profession or role does your character fulfill or is otherwise destined to?") + + perks = forms.MultipleChoiceField(choices=PERKS, help_text="What extraordinary abilities does your character possess?") + description = forms.CharField(widget=forms.Textarea(attrs={'rows': 3}), max_length=2048, min_length=160, required=False) + + strength = forms.IntegerField(min_value=1, max_value=10) + perception = forms.IntegerField(min_value=1, max_value=10) + intelligence = forms.IntegerField(min_value=1, max_value=10) + dexterity = forms.IntegerField(min_value=1, max_value=10) + charisma = forms.IntegerField(min_value=1, max_value=10) + vitality = forms.IntegerField(min_value=1, max_value=10) + magic = forms.IntegerField(min_value=1, max_value=10) + + def __init__(self, *args, **kwargs): + # Do all the normal initizliation stuff that would otherwise be happening + super(ExtendedCharacterCreationForm, self).__init__(*args, **kwargs) + + # Given a pool of points, let's randomly distribute them across attributes. + # First get a list of attributes + attributes = ('strength', 'perception', 'intelligence', 'dexterity', 'charisma', 'vitality', 'magic') + # Distribute a random number of points across them + attrs = self.assign_attributes(attributes, 50, 1, 10) + # Initialize the form with the results of the point distribution + for field in attrs.keys(): + self.initial[field] = attrs[field] \ No newline at end of file diff --git a/evennia/web/website/templates/website/_menu.html b/evennia/web/website/templates/website/_menu.html index ea01404967..432b6e4827 100644 --- a/evennia/web/website/templates/website/_menu.html +++ b/evennia/web/website/templates/website/_menu.html @@ -43,7 +43,7 @@ folder and edit it to add/remove links to the menu.
  • diff --git a/evennia/web/website/templates/website/pagination.html b/evennia/web/website/templates/website/pagination.html new file mode 100644 index 0000000000..e2cb6bed73 --- /dev/null +++ b/evennia/web/website/templates/website/pagination.html @@ -0,0 +1,34 @@ +{% if page_obj %} + +{% endif %} \ No newline at end of file From 4ca72cc9fed119902ed11a15fff5388bf1bab497 Mon Sep 17 00:00:00 2001 From: Johnny Date: Fri, 5 Oct 2018 21:06:42 +0000 Subject: [PATCH 084/493] Adds template for character management list view. --- .../website/character_manage_list.html | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 evennia/web/website/templates/website/character_manage_list.html diff --git a/evennia/web/website/templates/website/character_manage_list.html b/evennia/web/website/templates/website/character_manage_list.html new file mode 100644 index 0000000000..f4e112fd7a --- /dev/null +++ b/evennia/web/website/templates/website/character_manage_list.html @@ -0,0 +1,38 @@ +{% extends "base.html" %} + +{% block titleblock %} +Manage Characters +{% endblock %} + +{% block content %} + +{% load addclass %} +
    +
    +
    +
    +
    +

    Manage Characters

    +
    + + {% for object in object_list %} +
    + +
    +

    {{ object.db_date_created }} +
    Delete +
    Edit

    +
    {{ object }} {% if object.subtitle %}{{ object.subtitle }}{% endif %}
    +

    {{ object.db.desc }}

    +
    +
    + {% endfor %} + +
    +
    +
    +
    +
    +{% endblock %} + + From c31a2f079618a5c3eeb3d6dd606dfdbf04bb7dce Mon Sep 17 00:00:00 2001 From: Johnny Date: Fri, 5 Oct 2018 21:07:07 +0000 Subject: [PATCH 085/493] Removes character update form. --- evennia/web/website/forms.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/evennia/web/website/forms.py b/evennia/web/website/forms.py index 937c6be6e8..8759d681e8 100644 --- a/evennia/web/website/forms.py +++ b/evennia/web/website/forms.py @@ -82,10 +82,6 @@ class CharacterForm(forms.Form): indices = [i for (i, value) in enumerate(buckets) if (value < min_points) or (value < max_points)] return buckets - -class CharacterUpdateForm(CharacterForm): - class Meta: - fields = ('description',) class ExtendedCharacterForm(CharacterForm): From d972251d5c1c49bf6eba81a2187bcb75fdcf44ea Mon Sep 17 00:00:00 2001 From: Johnny Date: Fri, 5 Oct 2018 21:07:44 +0000 Subject: [PATCH 086/493] Adds character management/deletion views and some other changes. --- evennia/web/website/views.py | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/evennia/web/website/views.py b/evennia/web/website/views.py index d25c3493ec..0ae996027c 100644 --- a/evennia/web/website/views.py +++ b/evennia/web/website/views.py @@ -8,13 +8,19 @@ templates on the fly. from django.contrib.admin.sites import site from django.conf import settings from django.contrib.auth import authenticate +from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.admin.views.decorators import staff_member_required +from django.db.models.functions import Lower from django.shortcuts import render +from django.urls import reverse_lazy +from django.views.generic import View, TemplateView, ListView, DetailView, FormView +from django.views.generic.edit import DeleteView from evennia import SESSION_HANDLER from evennia.objects.models import ObjectDB from evennia.accounts.models import AccountDB from evennia.utils import logger +from evennia.web.website.forms import AccountForm, CharacterForm from django.contrib.auth import login from django.utils.text import slugify @@ -185,23 +191,37 @@ class AccountCreationView(FormView): class CharacterManageView(LoginRequiredMixin, ListView): model = ObjectDB + paginate_by = 10 + template_name = 'website/character_manage_list.html' def get_queryset(self): # Get IDs of characters owned by account ids = [getattr(x, 'id') for x in self.request.user.db._playable_characters] # Return a queryset consisting of those characters - return self.model.filter(id__in=ids) + return self.model.objects.filter(id__in=ids).order_by(Lower('db_key')) class CharacterUpdateView(LoginRequiredMixin, FormView): - form_class = CharacterUpdateForm + form_class = CharacterForm template_name = 'website/generic_form.html' - success_url = '/'#reverse_lazy('character-manage') + success_url = reverse_lazy('manage-characters') + fields = ('description',) + +class CharacterDeleteView(LoginRequiredMixin, ObjectDetailView, DeleteView): + model = ObjectDB + + def get_queryset(self): + # Restrict characters available for deletion to those owned by + # the authenticated account + ids = [getattr(x, 'id') for x in self.request.user.db._playable_characters] + + # Return a queryset consisting of those characters + return self.model.objects.filter(id__in=ids).order_by(Lower('db_key')) class CharacterCreateView(LoginRequiredMixin, FormView): form_class = CharacterForm - template_name = 'website/chargen_form.html' - success_url = '/'#reverse_lazy('character-manage') + template_name = 'website/character_create_form.html' + success_url = reverse_lazy('manage-characters') def form_valid(self, form): # Get account ref From d52ff85a50a77faca113a60fa1b73709782623b1 Mon Sep 17 00:00:00 2001 From: Johnny Date: Fri, 5 Oct 2018 21:08:00 +0000 Subject: [PATCH 087/493] Adds links to charman views. --- evennia/web/website/templates/website/_menu.html | 2 +- evennia/web/website/urls.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/evennia/web/website/templates/website/_menu.html b/evennia/web/website/templates/website/_menu.html index 9874b7ef4a..90eabba4eb 100644 --- a/evennia/web/website/templates/website/_menu.html +++ b/evennia/web/website/templates/website/_menu.html @@ -44,7 +44,7 @@ folder and edit it to add/remove links to the menu.