Resolves merge conflict.

This commit is contained in:
Johnny 2018-10-09 22:23:45 +00:00
commit da31aada10
15 changed files with 154 additions and 78 deletions

View file

@ -7,7 +7,7 @@
# Usage:
# cd to a folder where you want your game data to be (or where it already is).
#
# docker run -it -p 4000:4000 -p 4001:4001 -p 4005:4005 -v $PWD:/usr/src/game evennia/evennia
# docker run -it --rm -p 4000:4000 -p 4001:4001 -p 4005:4005 -v $PWD:/usr/src/game evennia/evennia
#
# (If your OS does not support $PWD, replace it with the full path to your current
# folder).
@ -15,6 +15,14 @@
# You will end up in a shell where the `evennia` command is available. From here you
# can install and run the game normally. Use Ctrl-D to exit the evennia docker container.
#
# You can also start evennia directly by passing arguments to the folder:
#
# docker run -it --rm -p 4000:4000 -p 4001:4001 -p 4005:4005 -v $PWD:/usr/src/game evennia/evennia evennia start -l
#
# This will start Evennia running as the core process of the container. Note that you *must* use -l
# or one of the foreground modes (like evennia ipstart) since otherwise the container will immediately
# die since no foreground process keeps it up.
#
# The evennia/evennia base image is found on DockerHub and can also be used
# as a base for creating your own custom containerized Evennia game. For more
# info, see https://github.com/evennia/evennia/wiki/Running%20Evennia%20in%20Docker .
@ -58,7 +66,7 @@ WORKDIR /usr/src/game
ENV PS1 "evennia|docker \w $ "
# startup a shell when we start the container
ENTRYPOINT bash -c "source /usr/src/evennia/bin/unix/evennia-docker-start.sh"
ENTRYPOINT ["/usr/src/evennia/bin/unix/evennia-docker-start.sh"]
# expose the telnet, webserver and websocket client ports
EXPOSE 4000 4001 4005

16
bin/unix/evennia-docker-start.sh Normal file → Executable file
View file

@ -1,10 +1,18 @@
#! /bin/bash
#! /bin/sh
# called by the Dockerfile to start the server in docker mode
# remove leftover .pid files (such as from when dropping the container)
rm /usr/src/game/server/*.pid >& /dev/null || true
# start evennia server; log to server.log but also output to stdout so it can
# be viewed with docker-compose logs
exec 3>&1; evennia start -l
PS1="evennia|docker \w $ "
cmd="$@"
output="Docker starting with argument '$cmd' ..."
if test -z $cmd; then
cmd="bash"
output="No argument given, starting shell ..."
fi
echo $output
exec 3>&1; $cmd

View file

@ -1,9 +1,10 @@
# -*- coding: utf-8 -*-
from mock import Mock
from mock import Mock, MagicMock
from random import randint
from unittest import TestCase
from django.test import override_settings
from evennia.accounts.accounts import AccountSessionHandler
from evennia.accounts.accounts import DefaultAccount
from evennia.server.session import Session
@ -16,9 +17,15 @@ class TestAccountSessionHandler(TestCase):
"Check AccountSessionHandler class"
def setUp(self):
self.account = create.create_account("TestAccount%s" % randint(0, 999999), email="test@test.com", password="testpassword", typeclass=DefaultAccount)
self.account = create.create_account(
"TestAccount%s" % randint(0, 999999), email="test@test.com",
password="testpassword", typeclass=DefaultAccount)
self.handler = AccountSessionHandler(self.account)
def tearDown(self):
if hasattr(self, 'account'):
self.account.delete()
def test_get(self):
"Check get method"
self.assertEqual(self.handler.get(), [])
@ -26,24 +33,24 @@ class TestAccountSessionHandler(TestCase):
import evennia.server.sessionhandler
s1 = Session()
s1 = MagicMock()
s1.logged_in = True
s1.uid = self.account.uid
evennia.server.sessionhandler.SESSIONS[s1.uid] = s1
s2 = Session()
s2 = MagicMock()
s2.logged_in = True
s2.uid = self.account.uid + 1
evennia.server.sessionhandler.SESSIONS[s2.uid] = s2
s3 = Session()
s3 = MagicMock()
s3.logged_in = False
s3.uid = self.account.uid + 2
evennia.server.sessionhandler.SESSIONS[s3.uid] = s3
self.assertEqual(self.handler.get(), [s1])
self.assertEqual(self.handler.get(self.account.uid), [s1])
self.assertEqual(self.handler.get(self.account.uid + 1), [])
self.assertEqual([s.uid for s in self.handler.get()], [s1.uid])
self.assertEqual([s.uid for s in [self.handler.get(self.account.uid)]], [s1.uid])
self.assertEqual([s.uid for s in self.handler.get(self.account.uid + 1)], [])
def test_all(self):
"Check all method"
@ -58,7 +65,7 @@ class TestDefaultAccount(TestCase):
"Check DefaultAccount class"
def setUp(self):
self.s1 = Session()
self.s1 = MagicMock()
self.s1.puppet = None
self.s1.sessid = 0
@ -124,6 +131,10 @@ class TestDefaultAccount(TestCase):
result, error = DefaultAccount.validate_username('xx')
self.assertFalse(result, "2-character username passed validation.")
def tearDown(self):
if hasattr(self, "account"):
self.account.delete()
def test_password_validation(self):
"Check password validators deny bad passwords"
@ -135,7 +146,6 @@ class TestDefaultAccount(TestCase):
"Check validators allow sufficiently complex passwords"
for better in ('Mxyzptlk', "j0hn, i'M 0n1y d4nc1nG"):
self.assertTrue(self.account.validate_password(better, account=self.account)[0])
self.account.delete()
def test_password_change(self):
"Check password setting and validation is working as expected"
@ -173,7 +183,9 @@ class TestDefaultAccount(TestCase):
import evennia.server.sessionhandler
account = create.create_account("TestAccount%s" % randint(0, 999999), email="test@test.com", password="testpassword", typeclass=DefaultAccount)
account = create.create_account(
"TestAccount%s" % randint(0, 999999), email="test@test.com",
password="testpassword", typeclass=DefaultAccount)
self.s1.uid = account.uid
evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1
@ -195,10 +207,7 @@ class TestDefaultAccount(TestCase):
self.s1.uid = account.uid
evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1
self.s1.puppet = None
self.s1.logged_in = True
self.s1.data_out = Mock(return_value=None)
self.s1.data_out = MagicMock()
obj = Mock()
obj.access = Mock(return_value=False)
@ -207,6 +216,7 @@ class TestDefaultAccount(TestCase):
self.assertTrue(self.s1.data_out.call_args[1]['text'].startswith("You don't have permission to puppet"))
self.assertIsNone(obj.at_post_puppet.call_args)
@override_settings(MULTISESSION_MODE=0)
def test_puppet_object_joining_other_session(self):
"Check puppet_object method called, joining other session"
@ -218,15 +228,16 @@ class TestDefaultAccount(TestCase):
self.s1.puppet = None
self.s1.logged_in = True
self.s1.data_out = Mock(return_value=None)
self.s1.data_out = MagicMock()
obj = Mock()
obj.access = Mock(return_value=True)
obj.account = account
obj.sessions.all = MagicMock(return_value=[self.s1])
account.puppet_object(self.s1, obj)
# works because django.conf.settings.MULTISESSION_MODE is not in (1, 3)
self.assertTrue(self.s1.data_out.call_args[1]['text'].endswith("from another of your sessions."))
self.assertTrue(self.s1.data_out.call_args[1]['text'].endswith("from another of your sessions.|n"))
self.assertTrue(obj.at_post_puppet.call_args[1] == {})
def test_puppet_object_already_puppeted(self):
@ -235,6 +246,7 @@ class TestDefaultAccount(TestCase):
import evennia.server.sessionhandler
account = create.create_account("TestAccount%s" % randint(0, 999999), email="test@test.com", password="testpassword", typeclass=DefaultAccount)
self.account = account
self.s1.uid = account.uid
evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1

View file

@ -23,7 +23,7 @@ from builtins import range
import time
from django.conf import settings
from evennia.server.sessionhandler import SESSIONS
from evennia.utils import utils, create, search, evtable
from evennia.utils import utils, create, logger, search, evtable
COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS)
@ -171,6 +171,7 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS):
new_character.db.desc = "This is a character."
self.msg("Created new character %s. Use |w@ic %s|n to enter the game as this character."
% (new_character.key, new_character.key))
logger.log_sec('Character Created: %s (Caller: %s, IP: %s).' % (new_character, account, self.session.address))
class CmdCharDelete(COMMAND_DEFAULT_CLASS):
@ -214,6 +215,7 @@ class CmdCharDelete(COMMAND_DEFAULT_CLASS):
caller.db._playable_characters = [pc for pc in caller.db._playable_characters if pc != delobj]
delobj.delete()
self.msg("Character '%s' was permanently deleted." % key)
logger.log_sec('Character Deleted: %s (Caller: %s, IP: %s).' % (key, account, self.session.address))
else:
self.msg("Deletion was aborted.")
del caller.ndb._char_to_delete
@ -279,8 +281,10 @@ class CmdIC(COMMAND_DEFAULT_CLASS):
try:
account.puppet_object(session, new_character)
account.db._last_puppet = new_character
logger.log_sec('Puppet Success: (Caller: %s, Target: %s, IP: %s).' % (account, new_character, self.session.address))
except RuntimeError as exc:
self.msg("|rYou cannot become |C%s|n: %s" % (new_character.name, exc))
logger.log_sec('Puppet Failed: %s (Caller: %s, Target: %s, IP: %s).' % (exc, account, new_character, self.session.address))
# note that this is inheriting from MuxAccountLookCommand,
@ -641,6 +645,7 @@ class CmdPassword(COMMAND_DEFAULT_CLASS):
account.set_password(newpass)
account.save()
self.msg("Password changed.")
logger.log_sec('Password Changed: %s (Caller: %s, IP: %s).' % (account, account, self.session.address))
class CmdQuit(COMMAND_DEFAULT_CLASS):

View file

@ -9,7 +9,7 @@ import re
from django.conf import settings
from evennia.server.sessionhandler import SESSIONS
from evennia.server.models import ServerConfig
from evennia.utils import evtable, search, class_from_module
from evennia.utils import evtable, logger, search, class_from_module
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
@ -96,6 +96,9 @@ class CmdBoot(COMMAND_DEFAULT_CLASS):
session.msg(feedback)
session.account.disconnect_session_from_account(session)
if pobj and boot_list:
logger.log_sec('Booted: %s (Reason: %s, Caller: %s, IP: %s).' % (pobj, reason, caller, self.session.address))
# regex matching IP addresses with wildcards, eg. 233.122.4.*
IPREGEX = re.compile(r"[0-9*]{1,3}\.[0-9*]{1,3}\.[0-9*]{1,3}\.[0-9*]{1,3}")
@ -203,6 +206,7 @@ class CmdBan(COMMAND_DEFAULT_CLASS):
banlist.append(bantup)
ServerConfig.objects.conf('server_bans', banlist)
self.caller.msg("%s-Ban |w%s|n was added." % (typ, ban))
logger.log_sec('Banned %s: %s (Caller: %s, IP: %s).' % (typ, ban.strip(), self.caller, self.session.address))
class CmdUnban(COMMAND_DEFAULT_CLASS):
@ -246,8 +250,10 @@ class CmdUnban(COMMAND_DEFAULT_CLASS):
ban = banlist[num - 1]
del banlist[num - 1]
ServerConfig.objects.conf('server_bans', banlist)
value = " ".join([s for s in ban[:2]])
self.caller.msg("Cleared ban %s: %s" %
(num, " ".join([s for s in ban[:2]])))
(num, value))
logger.log_sec('Unbanned: %s (Caller: %s, IP: %s).' % (value.strip(), self.caller, self.session.address))
class CmdDelAccount(COMMAND_DEFAULT_CLASS):
@ -317,6 +323,7 @@ class CmdDelAccount(COMMAND_DEFAULT_CLASS):
if reason:
string += " Reason given:\n '%s'" % reason
account.msg(string)
logger.log_sec('Account Deleted: %s (Reason: %s, Caller: %s, IP: %s).' % (account, reason, caller, self.session.address))
account.delete()
self.msg("Account %s was successfully deleted." % uname)
@ -428,9 +435,9 @@ class CmdNewPassword(COMMAND_DEFAULT_CLASS):
account = caller.search_account(self.lhs)
if not account:
return
newpass = self.rhs
# Validate password
validated, error = account.validate_password(newpass)
if not validated:
@ -438,13 +445,14 @@ class CmdNewPassword(COMMAND_DEFAULT_CLASS):
string = "\n".join(errors)
caller.msg(string)
return
account.set_password(newpass)
account.save()
self.msg("%s - new password set to '%s'." % (account.name, newpass))
if account.character != caller:
account.msg("%s has changed your password to '%s'." % (caller.name,
newpass))
logger.log_sec('Password Changed: %s (Caller: %s, IP: %s).' % (account, caller, self.session.address))
class CmdPerm(COMMAND_DEFAULT_CLASS):
@ -526,6 +534,7 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
else:
caller_result.append("\nPermission %s removed from %s (if they existed)." % (perm, obj.name))
target_result.append("\n%s revokes the permission(s) %s from you." % (caller.name, perm))
logger.log_sec('Permissions Deleted: %s, %s (Caller: %s, IP: %s).' % (perm, obj, caller, self.session.address))
else:
# add a new permission
permissions = obj.permissions.all()
@ -547,6 +556,8 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
caller_result.append("\nPermission '%s' given to %s (%s)." % (perm, obj.name, plystring))
target_result.append("\n%s gives you (%s, %s) the permission '%s'."
% (caller.name, obj.name, plystring, perm))
logger.log_sec('Permissions Added: %s, %s (Caller: %s, IP: %s).' % (obj, perm, caller, self.session.address))
caller.msg("".join(caller_result).strip())
if target_result:
obj.msg("".join(target_result).strip())

View file

@ -2889,7 +2889,8 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS):
"use the 'exec' prototype key.")
return None
try:
protlib.validate_prototype(prototype)
# we homogenize first, to be more lenient
protlib.validate_prototype(protlib.homogenize_prototype(prototype))
except RuntimeError as err:
self.caller.msg(str(err))
return

View file

@ -14,7 +14,7 @@ from evennia.accounts.models import AccountDB
from evennia.accounts import bots
from evennia.comms.channelhandler import CHANNELHANDLER
from evennia.locks.lockhandler import LockException
from evennia.utils import create, utils, evtable
from evennia.utils import create, logger, utils, evtable
from evennia.utils.utils import make_iter, class_from_module
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
@ -368,6 +368,7 @@ class CmdCdestroy(COMMAND_DEFAULT_CLASS):
channel.delete()
CHANNELHANDLER.update()
self.msg("Channel '%s' was destroyed." % channel_key)
logger.log_sec('Channel Deleted: %s (Caller: %s, IP: %s).' % (channel_key, caller, self.session.address))
class CmdCBoot(COMMAND_DEFAULT_CLASS):
@ -433,6 +434,8 @@ class CmdCBoot(COMMAND_DEFAULT_CLASS):
# disconnect account
channel.disconnect(account)
CHANNELHANDLER.update()
logger.log_sec('Channel Boot: %s (Channel: %s, Reason: %s, Caller: %s, IP: %s).' % (
account, channel, reason, self.caller, self.session.address))
class CmdCemit(COMMAND_DEFAULT_CLASS):

View file

@ -6,6 +6,8 @@ Handling storage of prototypes, both database-based ones (DBPrototypes) and thos
"""
import re
import hashlib
import time
from ast import literal_eval
from django.conf import settings
from evennia.scripts.scripts import DefaultScript
@ -13,7 +15,7 @@ from evennia.objects.models import ObjectDB
from evennia.utils.create import create_script
from evennia.utils.utils import (
all_from_module, make_iter, is_iter, dbid_to_obj, callables_from_module,
get_all_typeclasses, to_str, dbref, justify)
get_all_typeclasses, to_str, dbref, justify, class_from_module)
from evennia.locks.lockhandler import validate_lockstring, check_lockstring
from evennia.utils import logger
from evennia.utils import inlinefuncs, dbserialize
@ -47,8 +49,8 @@ class ValidationError(RuntimeError):
def homogenize_prototype(prototype, custom_keys=None):
"""
Homogenize the more free-form prototype (where undefined keys are non-category attributes)
into the stricter form using `attrs` required by the system.
Homogenize the more free-form prototype supported pre Evennia 0.7 into the stricter form.
Args:
prototype (dict): Prototype.
@ -56,18 +58,45 @@ def homogenize_prototype(prototype, custom_keys=None):
the default reserved keys.
Returns:
homogenized (dict): Prototype where all non-identified keys grouped as attributes.
homogenized (dict): Prototype where all non-identified keys grouped as attributes and other
homogenizations like adding missing prototype_keys and setting a default typeclass.
"""
reserved = _PROTOTYPE_RESERVED_KEYS + (custom_keys or ())
attrs = list(prototype.get('attrs', [])) # break reference
tags = make_iter(prototype.get('tags', []))
homogenized_tags = []
homogenized = {}
for key, val in prototype.items():
if key in reserved:
homogenized[key] = val
if key == 'tags':
for tag in tags:
if not is_iter(tag):
homogenized_tags.append((tag, None, None))
else:
homogenized_tags.append(tag)
else:
homogenized[key] = val
else:
# unassigned keys -> attrs
attrs.append((key, val, None, ''))
if attrs:
homogenized['attrs'] = attrs
if homogenized_tags:
homogenized['tags'] = homogenized_tags
# add required missing parts that had defaults before
if "prototype_key" not in prototype:
# assign a random hash as key
homogenized["prototype_key"] = "prototype-{}".format(
hashlib.md5(str(time.time())).hexdigest()[:7])
if "typeclass" not in prototype and "prototype_parent" not in prototype:
homogenized["typeclass"] = settings.BASE_OBJECT_TYPECLASS
return homogenized
@ -76,9 +105,11 @@ def homogenize_prototype(prototype, custom_keys=None):
for mod in settings.PROTOTYPE_MODULES:
# to remove a default prototype, override it with an empty dict.
# internally we store as (key, desc, locks, tags, prototype_dict)
prots = [(prototype_key.lower(), homogenize_prototype(prot))
for prototype_key, prot in all_from_module(mod).items()
if prot and isinstance(prot, dict)]
prots = []
for variable_name, prot in all_from_module(mod).items():
if "prototype_key" not in prot:
prot['prototype_key'] = variable_name.lower()
prots.append((prot['prototype_key'], homogenize_prototype(prot)))
# assign module path to each prototype_key for easy reference
_MODULE_PROTOTYPE_MODULES.update({prototype_key.lower(): mod for prototype_key, _ in prots})
# make sure the prototype contains all meta info
@ -432,11 +463,13 @@ def validate_prototype(prototype, protkey=None, protparents=None,
_flags['warnings'].append("Prototype {} can only be used as a mixin since it lacks "
"a typeclass or a prototype_parent.".format(protkey))
if (strict and typeclass and typeclass not
in get_all_typeclasses("evennia.objects.models.ObjectDB")):
_flags['errors'].append(
"Prototype {} is based on typeclass {}, which could not be imported!".format(
protkey, typeclass))
if strict and typeclass:
try:
class_from_module(typeclass)
except ImportError as err:
_flags['errors'].append(
"{}: Prototype {} is based on typeclass {}, which could not be imported!".format(
err, protkey, typeclass))
# recursively traverese prototype_parent chain

View file

@ -259,11 +259,11 @@ def prototype_from_object(obj):
if aliases:
prot['aliases'] = aliases
tags = [(tag.db_key, tag.db_category, tag.db_data)
for tag in obj.tags.get(return_tagobj=True, return_list=True) if tag]
for tag in obj.tags.all(return_objs=True)]
if tags:
prot['tags'] = tags
attrs = [(attr.key, attr.value, attr.category, ';'.join(attr.locks.all()))
for attr in obj.attributes.get(return_obj=True, return_list=True) if attr]
for attr in obj.attributes.all()]
if attrs:
prot['attrs'] = attrs
@ -659,6 +659,10 @@ def spawn(*prototypes, **kwargs):
# get available protparents
protparents = {prot['prototype_key'].lower(): prot for prot in protlib.search_prototype()}
if not kwargs.get("only_validate"):
# homogenization to be more lenient about prototype format when entering the prototype manually
prototypes = [protlib.homogenize_prototype(prot) for prot in prototypes]
# overload module's protparents with specifically given protparents
# we allow prototype_key to be the key of the protparent dict, to allow for module-level
# prototype imports. We need to insert prototype_key in this case
@ -711,7 +715,7 @@ def spawn(*prototypes, **kwargs):
val = prot.pop("tags", [])
tags = []
for (tag, category, data) in tags:
for (tag, category, data) in val:
tags.append((init_spawn_value(val, str), category, data))
prototype_key = prototype.get('prototype_key', None)
@ -730,7 +734,7 @@ def spawn(*prototypes, **kwargs):
val = make_iter(prot.pop("attrs", []))
attributes = []
for (attrname, value, category, locks) in val:
attributes.append((attrname, init_spawn_value(val), category, locks))
attributes.append((attrname, init_spawn_value(value), category, locks))
simple_attributes = []
for key, value in ((key, value) for key, value in prot.items()

View file

@ -134,6 +134,7 @@ class TestUtils(EvenniaTest):
'prototype_key': Something,
'prototype_locks': 'spawn:all();edit:all()',
'prototype_tags': [],
'tags': [(u'footag', u'foocategory', None)],
'typeclass': 'evennia.objects.objects.DefaultObject'})
self.assertEqual(old_prot,
@ -182,6 +183,7 @@ class TestUtils(EvenniaTest):
'typeclass': ('evennia.objects.objects.DefaultObject',
'evennia.objects.objects.DefaultObject', 'KEEP'),
'aliases': {'foo': ('foo', None, 'REMOVE')},
'tags': {u'footag': ((u'footag', u'foocategory', None), None, 'REMOVE')},
'prototype_desc': ('Built from Obj',
'New version of prototype', 'UPDATE'),
'permissions': {"Builder": (None, 'Builder', 'ADD')}
@ -200,6 +202,7 @@ class TestUtils(EvenniaTest):
'prototype_key': 'UPDATE',
'prototype_locks': 'KEEP',
'prototype_tags': 'KEEP',
'tags': 'REMOVE',
'typeclass': 'KEEP'}
)
@ -384,8 +387,9 @@ class TestPrototypeStorage(EvenniaTest):
prot3 = protlib.create_prototype(**self.prot3)
# partial match
self.assertEqual(list(protlib.search_prototype("prot")), [prot1b, prot2, prot3])
self.assertEqual(list(protlib.search_prototype(tags="foo1")), [prot1b, prot2, prot3])
with mock.patch("evennia.prototypes.prototypes._MODULE_PROTOTYPES", {}):
self.assertEqual(list(protlib.search_prototype("prot")), [prot1b, prot2, prot3])
self.assertEqual(list(protlib.search_prototype(tags="foo1")), [prot1b, prot2, prot3])
self.assertTrue(str(unicode(protlib.list_prototypes(self.char1))))

View file

@ -13,6 +13,7 @@ always be sure of what you have changed and what is default behaviour.
"""
from builtins import range
from django.urls import reverse_lazy
import os
import sys
@ -697,9 +698,9 @@ ROOT_URLCONF = 'web.urls'
# Where users are redirected after logging in via contrib.auth.login.
LOGIN_REDIRECT_URL = '/'
# Where to redirect users when using the @login_required decorator.
LOGIN_URL = '/accounts/login'
LOGIN_URL = reverse_lazy('login')
# Where to redirect users who wish to logout.
LOGOUT_URL = '/accounts/login'
LOGOUT_URL = reverse_lazy('logout')
# URL that handles the media served from MEDIA_ROOT.
# Example: "http://media.lawrence.com"
MEDIA_URL = '/media/'

View file

@ -668,7 +668,7 @@ class AttributeHandler(object):
def all(self, accessing_obj=None, default_access=True):
"""
Return all Attribute objects on this object.
Return all Attribute objects on this object, regardless of category.
Args:
accessing_obj (object, optional): Check the `attrread`

View file

@ -345,13 +345,14 @@ class TagHandler(object):
self._catcache = {}
self._cache_complete = False
def all(self, return_key_and_category=False):
def all(self, return_key_and_category=False, return_objs=False):
"""
Get all tags in this handler, regardless of category.
Args:
return_key_and_category (bool, optional): Return a list of
tuples `[(key, category), ...]`.
return_objs (bool, optional): Return tag objects.
Returns:
tags (list): A list of tag keys `[tagkey, tagkey, ...]` or
@ -365,6 +366,8 @@ class TagHandler(object):
if return_key_and_category:
# return tuple (key, category)
return [(to_str(tag.db_key), to_str(tag.db_category)) for tag in tags]
elif return_objs:
return tags
else:
return [to_str(tag.db_key) for tag in tags]

View file

@ -8,6 +8,7 @@ let defaultin_plugin = (function () {
//
// handle the default <enter> key triggering onSend()
var onKeydown = function (event) {
$("#inputfield").focus();
if ( (event.which === 13) && (!event.shiftKey) ) { // Enter Key without shift
var inputfield = $("#inputfield");
var outtext = inputfield.val();

View file

@ -43,14 +43,6 @@ let history_plugin = (function () {
history_pos = 0;
}
//
// Go to the last history line
var end = function () {
// move to the end of the history stack
history_pos = 0;
return history[history.length -1];
}
//
// Add input to the scratch line
var scratch = function (input) {
@ -69,28 +61,17 @@ let history_plugin = (function () {
var history_entry = null;
var inputfield = $("#inputfield");
if (inputfield[0].selectionStart == inputfield.val().length) {
// Only process up/down arrow if cursor is at the end of the line.
if (code === 38) { // Arrow up
history_entry = back();
}
else if (code === 40) { // Arrow down
history_entry = fwd();
}
if (code === 38) { // Arrow up
history_entry = back();
}
else if (code === 40) { // Arrow down
history_entry = fwd();
}
if (history_entry !== null) {
// Doing a history navigation; replace the text in the input.
inputfield.val(history_entry);
}
else {
// Save the current contents of the input to the history scratch area.
setTimeout(function () {
// Need to wait until after the key-up to capture the value.
scratch(inputfield.val());
end();
}, 0);
}
return false;
}
@ -99,6 +80,7 @@ let history_plugin = (function () {
// Listen for onSend lines to add to history
var onSend = function (line) {
add(line);
return null; // we are not returning an altered input line
}
//