Fix examine using wrong cmdset target for accounts. Resolve #1886
This commit is contained in:
parent
0ef2e667e4
commit
bc5210eb9c
6 changed files with 139 additions and 106 deletions
|
|
@ -180,7 +180,7 @@ def import_cmdset(path, cmdsetobj, emit_to_obj=None, no_logging=False):
|
||||||
except ImportError as exc:
|
except ImportError as exc:
|
||||||
if len(trace()) > 2:
|
if len(trace()) > 2:
|
||||||
# error in module, make sure to not hide it.
|
# error in module, make sure to not hide it.
|
||||||
_, _, tb = sys.exc_info()
|
dum, dum, tb = sys.exc_info()
|
||||||
raise exc.with_traceback(tb)
|
raise exc.with_traceback(tb)
|
||||||
else:
|
else:
|
||||||
# try next suggested path
|
# try next suggested path
|
||||||
|
|
@ -191,7 +191,7 @@ def import_cmdset(path, cmdsetobj, emit_to_obj=None, no_logging=False):
|
||||||
except AttributeError as exc:
|
except AttributeError as exc:
|
||||||
if len(trace()) > 2:
|
if len(trace()) > 2:
|
||||||
# Attribute error within module, don't hide it
|
# Attribute error within module, don't hide it
|
||||||
_, _, tb = sys.exc_info()
|
dum, dum, tb = sys.exc_info()
|
||||||
raise exc.with_traceback(tb)
|
raise exc.with_traceback(tb)
|
||||||
else:
|
else:
|
||||||
errstring += _("\n(Unsuccessfully tried '%s')." % python_path)
|
errstring += _("\n(Unsuccessfully tried '%s')." % python_path)
|
||||||
|
|
|
||||||
|
|
@ -1596,7 +1596,7 @@ class CmdSetAttribute(ObjManipCommand):
|
||||||
or |c{ |n.
|
or |c{ |n.
|
||||||
|
|
||||||
Once you have stored a Python primitive as noted above, you can include
|
Once you have stored a Python primitive as noted above, you can include
|
||||||
|c[<key>]|n in <attr> to reference nested values in e.g. a list or dict.
|
|c[<key>]|n in <attr> to reference nested values in e.g. a list or dict.
|
||||||
|
|
||||||
Remember that if you use Python primitives like this, you must
|
Remember that if you use Python primitives like this, you must
|
||||||
write proper Python syntax too - notably you must include quotes
|
write proper Python syntax too - notably you must include quotes
|
||||||
|
|
@ -2352,7 +2352,6 @@ class CmdExamine(ObjManipCommand):
|
||||||
|
|
||||||
returns a string.
|
returns a string.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
string = "\n|wName/key|n: |c%s|n (%s)" % (obj.name, obj.dbref)
|
string = "\n|wName/key|n: |c%s|n (%s)" % (obj.name, obj.dbref)
|
||||||
if hasattr(obj, "aliases") and obj.aliases.all():
|
if hasattr(obj, "aliases") and obj.aliases.all():
|
||||||
string += "\n|wAliases|n: %s" % (", ".join(utils.make_iter(str(obj.aliases))))
|
string += "\n|wAliases|n: %s" % (", ".join(utils.make_iter(str(obj.aliases))))
|
||||||
|
|
@ -2534,9 +2533,11 @@ class CmdExamine(ObjManipCommand):
|
||||||
# If we don't have special info access, just look at the object instead.
|
# If we don't have special info access, just look at the object instead.
|
||||||
self.msg(caller.at_look(obj))
|
self.msg(caller.at_look(obj))
|
||||||
return
|
return
|
||||||
|
obj_session = obj.sessions.get()[0] if obj.sessions.count() else None
|
||||||
|
|
||||||
# using callback for printing result whenever function returns.
|
# using callback for printing result whenever function returns.
|
||||||
get_and_merge_cmdsets(
|
get_and_merge_cmdsets(
|
||||||
obj, self.session, self.account, obj, "object", self.raw_string
|
obj, obj_session, self.account, obj, "object", self.raw_string
|
||||||
).addCallback(get_cmdset_callback)
|
).addCallback(get_cmdset_callback)
|
||||||
else:
|
else:
|
||||||
self.msg("You need to supply a target to examine.")
|
self.msg("You need to supply a target to examine.")
|
||||||
|
|
@ -2578,15 +2579,25 @@ class CmdExamine(ObjManipCommand):
|
||||||
# we are only interested in specific attributes
|
# we are only interested in specific attributes
|
||||||
caller.msg(self.format_attributes(obj, attrname, crop=False))
|
caller.msg(self.format_attributes(obj, attrname, crop=False))
|
||||||
else:
|
else:
|
||||||
|
session = obj.sessions.get()[0]
|
||||||
if obj.sessions.count():
|
if obj.sessions.count():
|
||||||
mergemode = "session"
|
mergemode = "session"
|
||||||
elif self.account_mode:
|
elif self.account_mode:
|
||||||
mergemode = "account"
|
mergemode = "account"
|
||||||
else:
|
else:
|
||||||
mergemode = "object"
|
mergemode = "object"
|
||||||
|
|
||||||
|
account = None
|
||||||
|
objct = None
|
||||||
|
if self.account_mode:
|
||||||
|
account = obj
|
||||||
|
else:
|
||||||
|
account = obj.account
|
||||||
|
objct = obj
|
||||||
|
|
||||||
# using callback to print results whenever function returns.
|
# using callback to print results whenever function returns.
|
||||||
get_and_merge_cmdsets(
|
get_and_merge_cmdsets(
|
||||||
obj, self.session, self.account, obj, mergemode, self.raw_string
|
obj, session, account, objct, mergemode, self.raw_string
|
||||||
).addCallback(get_cmdset_callback)
|
).addCallback(get_cmdset_callback)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ system but they don't necessarily need to follow each other in the exact
|
||||||
sequence.
|
sequence.
|
||||||
|
|
||||||
Each state module must make a class `State` available in the global scope. This
|
Each state module must make a class `State` available in the global scope. This
|
||||||
should be a child of `evennia.contribs.evscaperoom.state.BaseState`. The
|
should be a child of `evennia.contrib.evscaperoom.state.BaseState`. The
|
||||||
methods on this class will be called to initialize the state and clean up etc.
|
methods on this class will be called to initialize the state and clean up etc.
|
||||||
There are no other restrictions on the module.
|
There are no other restrictions on the module.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -417,7 +417,7 @@ CMDSET_CHARACTER = "commands.default_cmdsets.CharacterCmdSet"
|
||||||
# Command set for accounts without a character (ooc)
|
# Command set for accounts without a character (ooc)
|
||||||
CMDSET_ACCOUNT = "commands.default_cmdsets.AccountCmdSet"
|
CMDSET_ACCOUNT = "commands.default_cmdsets.AccountCmdSet"
|
||||||
# Location to search for cmdsets if full path not given
|
# Location to search for cmdsets if full path not given
|
||||||
CMDSET_PATHS = ["commands", "evennia", "contribs"]
|
CMDSET_PATHS = ["commands", "evennia", "evennia.contrib"]
|
||||||
# Fallbacks for cmdset paths that fail to load. Note that if you change the path for your
|
# Fallbacks for cmdset paths that fail to load. Note that if you change the path for your
|
||||||
# default cmdsets, you will also need to copy CMDSET_FALLBACKS after your change in your
|
# default cmdsets, you will also need to copy CMDSET_FALLBACKS after your change in your
|
||||||
# settings file for it to detect the change.
|
# settings file for it to detect the change.
|
||||||
|
|
|
||||||
|
|
@ -233,7 +233,7 @@ def read_batchfile(pythonpath, file_ending=".py"):
|
||||||
continue
|
continue
|
||||||
break
|
break
|
||||||
if not text and decoderr:
|
if not text and decoderr:
|
||||||
raise UnicodeDecodeError("\n".join(decoderr), bytearray(), 0, 0, '')
|
raise UnicodeDecodeError("\n".join(decoderr), bytearray(), 0, 0, "")
|
||||||
|
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,26 +8,24 @@ import textwrap
|
||||||
|
|
||||||
|
|
||||||
class TestBatchprocessorErrors(TestCase):
|
class TestBatchprocessorErrors(TestCase):
|
||||||
|
@mock.patch.object(utils, "pypath_to_realpath", return_value=[])
|
||||||
@mock.patch.object(utils, 'pypath_to_realpath', return_value=[])
|
|
||||||
def test_read_batchfile_raises_IOError(self, _):
|
def test_read_batchfile_raises_IOError(self, _):
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(IOError):
|
||||||
batchprocessors.read_batchfile('foopath')
|
batchprocessors.read_batchfile("foopath")
|
||||||
|
|
||||||
@mock.patch.object(utils, 'pypath_to_realpath', return_value=['pathfoo'])
|
@mock.patch.object(utils, "pypath_to_realpath", return_value=["pathfoo"])
|
||||||
@mock.patch.object(codecs, 'open', side_effect=ValueError('decodeerr'))
|
@mock.patch.object(codecs, "open", side_effect=ValueError("decodeerr"))
|
||||||
@mock.patch.object(batchprocessors, '_ENCODINGS', ['fooencoding'])
|
@mock.patch.object(batchprocessors, "_ENCODINGS", ["fooencoding"])
|
||||||
def test_read_batchfile_raises_UnicodeDecodeError(self, *_):
|
def test_read_batchfile_raises_UnicodeDecodeError(self, *_):
|
||||||
with self.assertRaises(UnicodeDecodeError, msg='decodeerr'):
|
with self.assertRaises(UnicodeDecodeError, msg="decodeerr"):
|
||||||
batchprocessors.read_batchfile('foopath')
|
batchprocessors.read_batchfile("foopath")
|
||||||
|
|
||||||
|
|
||||||
class TestBatchCommandProcessor(TestCase):
|
class TestBatchCommandProcessor(TestCase):
|
||||||
|
@mock.patch.object(batchprocessors, "read_batchfile")
|
||||||
@mock.patch.object(batchprocessors, 'read_batchfile')
|
|
||||||
def test_parses_2_commands(self, mocked_read):
|
def test_parses_2_commands(self, mocked_read):
|
||||||
mocked_read.return_value = textwrap.dedent(
|
mocked_read.return_value = textwrap.dedent(
|
||||||
r"""
|
r"""
|
||||||
@create rock
|
@create rock
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
@ -35,93 +33,102 @@ class TestBatchCommandProcessor(TestCase):
|
||||||
A big rock. You can tell is ancient.
|
A big rock. You can tell is ancient.
|
||||||
#
|
#
|
||||||
|
|
||||||
""")
|
"""
|
||||||
commands = batchprocessors.BATCHCMD.parse_file('foopath')
|
)
|
||||||
self.assertEqual([
|
commands = batchprocessors.BATCHCMD.parse_file("foopath")
|
||||||
'@create rock', '@set rock/desc =\nA big rock. You can tell is ancient.'],
|
self.assertEqual(
|
||||||
commands)
|
["@create rock", "@set rock/desc =\nA big rock. You can tell is ancient."], commands
|
||||||
|
)
|
||||||
|
|
||||||
@mock.patch.object(batchprocessors, 'read_batchfile')
|
@mock.patch.object(batchprocessors, "read_batchfile")
|
||||||
def test_parses_INSERT(self, mocked_read):
|
def test_parses_INSERT(self, mocked_read):
|
||||||
mocked_read.side_effect = [
|
mocked_read.side_effect = [
|
||||||
textwrap.dedent(r"""
|
textwrap.dedent(
|
||||||
|
r"""
|
||||||
@create sky
|
@create sky
|
||||||
#
|
#
|
||||||
#INSERT another.ev
|
#INSERT another.ev
|
||||||
#
|
#
|
||||||
@create sun
|
@create sun
|
||||||
#
|
#
|
||||||
"""),
|
"""
|
||||||
textwrap.dedent(r"""
|
),
|
||||||
|
textwrap.dedent(
|
||||||
|
r"""
|
||||||
@create bird
|
@create bird
|
||||||
#
|
#
|
||||||
@create cloud
|
@create cloud
|
||||||
#
|
#
|
||||||
""")
|
"""
|
||||||
|
),
|
||||||
]
|
]
|
||||||
commands = batchprocessors.BATCHCMD.parse_file('foopath')
|
commands = batchprocessors.BATCHCMD.parse_file("foopath")
|
||||||
|
self.assertEqual(commands, ["@create sky", "@create bird", "@create cloud", "@create sun"])
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
commands,
|
mocked_read.mock_calls,
|
||||||
['@create sky', '@create bird', '@create cloud', '@create sun'])
|
[mock.call("foopath", file_ending=".ev"), mock.call("another.ev", file_ending=".ev")],
|
||||||
self.assertEqual(mocked_read.mock_calls, [
|
)
|
||||||
mock.call('foopath', file_ending='.ev'),
|
|
||||||
mock.call('another.ev', file_ending='.ev')])
|
|
||||||
|
|
||||||
@mock.patch.object(batchprocessors, 'read_batchfile')
|
@mock.patch.object(batchprocessors, "read_batchfile")
|
||||||
def test_parses_INSERT_raises_IOError(self, mocked_read):
|
def test_parses_INSERT_raises_IOError(self, mocked_read):
|
||||||
mocked_read.side_effect = [
|
mocked_read.side_effect = [
|
||||||
textwrap.dedent(r"""
|
textwrap.dedent(
|
||||||
|
r"""
|
||||||
@create sky
|
@create sky
|
||||||
#
|
#
|
||||||
#INSERT x
|
#INSERT x
|
||||||
#
|
#
|
||||||
@create sun
|
@create sun
|
||||||
#
|
#
|
||||||
"""),
|
"""
|
||||||
IOError
|
),
|
||||||
|
IOError,
|
||||||
]
|
]
|
||||||
with self.assertRaises(IOError, msg='#INSERT x failed.'):
|
with self.assertRaises(IOError, msg="#INSERT x failed."):
|
||||||
batchprocessors.BATCHCMD.parse_file('foopath')
|
batchprocessors.BATCHCMD.parse_file("foopath")
|
||||||
self.assertEqual(mocked_read.mock_calls, [
|
self.assertEqual(
|
||||||
mock.call('foopath', file_ending='.ev'),
|
mocked_read.mock_calls,
|
||||||
mock.call('x', file_ending='.ev')])
|
[mock.call("foopath", file_ending=".ev"), mock.call("x", file_ending=".ev")],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestBatchCodeProcessor(TestCase):
|
class TestBatchCodeProcessor(TestCase):
|
||||||
|
@mock.patch.object(batchprocessors, "read_batchfile")
|
||||||
@mock.patch.object(batchprocessors, 'read_batchfile')
|
|
||||||
def test_parses_one_codeblock(self, mocked_read):
|
def test_parses_one_codeblock(self, mocked_read):
|
||||||
mocked_read.return_value = textwrap.dedent(
|
mocked_read.return_value = textwrap.dedent(
|
||||||
r"""
|
r"""
|
||||||
print("Hello")
|
print("Hello")
|
||||||
""")
|
"""
|
||||||
commands = batchprocessors.BATCHCODE.parse_file('foopath')
|
)
|
||||||
self.assertEqual([
|
commands = batchprocessors.BATCHCODE.parse_file("foopath")
|
||||||
'# batchcode code:\n\nprint("Hello")\n'],
|
self.assertEqual(['# batchcode code:\n\nprint("Hello")\n'], commands)
|
||||||
commands)
|
|
||||||
|
|
||||||
@mock.patch.object(batchprocessors, 'read_batchfile')
|
@mock.patch.object(batchprocessors, "read_batchfile")
|
||||||
def test_parses_codeblocks(self, mocked_read):
|
def test_parses_codeblocks(self, mocked_read):
|
||||||
mocked_read.return_value = textwrap.dedent(
|
mocked_read.return_value = textwrap.dedent(
|
||||||
r"""
|
r"""
|
||||||
#CODE
|
#CODE
|
||||||
print("Hello")
|
print("Hello")
|
||||||
#CODE
|
#CODE
|
||||||
a = 1
|
a = 1
|
||||||
b = [1,
|
b = [1,
|
||||||
2, 3]
|
2, 3]
|
||||||
""")
|
"""
|
||||||
commands = batchprocessors.BATCHCODE.parse_file('foopath')
|
)
|
||||||
self.assertEqual([
|
commands = batchprocessors.BATCHCODE.parse_file("foopath")
|
||||||
'# batchcode code:\n\n',
|
self.assertEqual(
|
||||||
'# batchcode code:\n\nprint("Hello")\n',
|
[
|
||||||
'# batchcode code:\n\na = 1\nb = [1,\n2, 3]\n'],
|
"# batchcode code:\n\n",
|
||||||
commands)
|
'# batchcode code:\n\nprint("Hello")\n',
|
||||||
|
"# batchcode code:\n\na = 1\nb = [1,\n2, 3]\n",
|
||||||
|
],
|
||||||
|
commands,
|
||||||
|
)
|
||||||
|
|
||||||
@mock.patch.object(batchprocessors, 'read_batchfile')
|
@mock.patch.object(batchprocessors, "read_batchfile")
|
||||||
def test_parses_header_and_two_codeblock(self, mocked_read):
|
def test_parses_header_and_two_codeblock(self, mocked_read):
|
||||||
mocked_read.return_value = textwrap.dedent(
|
mocked_read.return_value = textwrap.dedent(
|
||||||
r"""
|
r"""
|
||||||
#HEADER
|
#HEADER
|
||||||
a = 100
|
a = 100
|
||||||
#CODE
|
#CODE
|
||||||
|
|
@ -129,71 +136,86 @@ class TestBatchCodeProcessor(TestCase):
|
||||||
#CODE
|
#CODE
|
||||||
a += 100
|
a += 100
|
||||||
a == 100
|
a == 100
|
||||||
""")
|
"""
|
||||||
commands = batchprocessors.BATCHCODE.parse_file('foopath')
|
)
|
||||||
self.assertEqual([
|
commands = batchprocessors.BATCHCODE.parse_file("foopath")
|
||||||
'# batchcode header:\n\na = 100\n\n\n# batchcode code:\n\n',
|
self.assertEqual(
|
||||||
'# batchcode header:\n\na = 100\n\n\n# batchcode code:\n\na += 100\n',
|
[
|
||||||
'# batchcode header:\n\na = 100\n\n\n# batchcode code:\n\na += 100\na == 100\n'],
|
"# batchcode header:\n\na = 100\n\n\n# batchcode code:\n\n",
|
||||||
commands)
|
"# batchcode header:\n\na = 100\n\n\n# batchcode code:\n\na += 100\n",
|
||||||
|
"# batchcode header:\n\na = 100\n\n\n# batchcode code:\n\na += 100\na == 100\n",
|
||||||
|
],
|
||||||
|
commands,
|
||||||
|
)
|
||||||
|
|
||||||
@mock.patch.object(batchprocessors, 'read_batchfile')
|
@mock.patch.object(batchprocessors, "read_batchfile")
|
||||||
def test_parses_INSERT(self, mocked_read):
|
def test_parses_INSERT(self, mocked_read):
|
||||||
mocked_read.side_effect = [
|
mocked_read.side_effect = [
|
||||||
textwrap.dedent(r"""
|
textwrap.dedent(
|
||||||
|
r"""
|
||||||
a = 1
|
a = 1
|
||||||
#INSERT another.py
|
#INSERT another.py
|
||||||
"""),
|
"""
|
||||||
textwrap.dedent(r"""
|
),
|
||||||
|
textwrap.dedent(
|
||||||
|
r"""
|
||||||
print("Hello")
|
print("Hello")
|
||||||
""")
|
"""
|
||||||
|
),
|
||||||
]
|
]
|
||||||
commands = batchprocessors.BATCHCODE.parse_file('foopath')
|
commands = batchprocessors.BATCHCODE.parse_file("foopath")
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
commands, [
|
commands,
|
||||||
'# batchcode code:\n'
|
[
|
||||||
'\n'
|
"# batchcode code:\n"
|
||||||
'a = 1\n'
|
"\n"
|
||||||
'# batchcode insert (another.py):# batchcode code:\n'
|
"a = 1\n"
|
||||||
'\n'
|
"# batchcode insert (another.py):# batchcode code:\n"
|
||||||
'print("Hello")\n'
|
"\n"
|
||||||
'\n'])
|
'print("Hello")\n'
|
||||||
self.assertEqual(mocked_read.mock_calls, [
|
"\n"
|
||||||
mock.call('foopath', file_ending='.py'),
|
],
|
||||||
mock.call('another.py', file_ending='.py')])
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
mocked_read.mock_calls,
|
||||||
|
[mock.call("foopath", file_ending=".py"), mock.call("another.py", file_ending=".py")],
|
||||||
|
)
|
||||||
|
|
||||||
@mock.patch.object(batchprocessors, 'read_batchfile')
|
@mock.patch.object(batchprocessors, "read_batchfile")
|
||||||
def test_parses_INSERT_raises_IOError(self, mocked_read):
|
def test_parses_INSERT_raises_IOError(self, mocked_read):
|
||||||
mocked_read.side_effect = [
|
mocked_read.side_effect = [
|
||||||
textwrap.dedent(r"""
|
textwrap.dedent(
|
||||||
|
r"""
|
||||||
#INSERT x
|
#INSERT x
|
||||||
"""),
|
"""
|
||||||
IOError
|
),
|
||||||
|
IOError,
|
||||||
]
|
]
|
||||||
with self.assertRaises(IOError, msg='#INSERT x failed.'):
|
with self.assertRaises(IOError, msg="#INSERT x failed."):
|
||||||
batchprocessors.BATCHCODE.parse_file('foopath')
|
batchprocessors.BATCHCODE.parse_file("foopath")
|
||||||
self.assertEqual(mocked_read.mock_calls, [
|
self.assertEqual(
|
||||||
mock.call('foopath', file_ending='.py'),
|
mocked_read.mock_calls,
|
||||||
mock.call('x', file_ending='.py')])
|
[mock.call("foopath", file_ending=".py"), mock.call("x", file_ending=".py")],
|
||||||
|
)
|
||||||
|
|
||||||
@mock.patch('builtins.exec')
|
@mock.patch("builtins.exec")
|
||||||
def test_execs_codeblock(self, mocked_exec):
|
def test_execs_codeblock(self, mocked_exec):
|
||||||
err = batchprocessors.BATCHCODE.code_exec(
|
err = batchprocessors.BATCHCODE.code_exec(
|
||||||
'# batchcode code:\n\nprint("Hello")\n',
|
'# batchcode code:\n\nprint("Hello")\n', extra_environ={}
|
||||||
extra_environ={})
|
)
|
||||||
self.assertIsNone(err)
|
self.assertIsNone(err)
|
||||||
|
|
||||||
@mock.patch('builtins.exec')
|
@mock.patch("builtins.exec")
|
||||||
def test_execs_codeblock_with_extra_environ(self, mocked_exec):
|
def test_execs_codeblock_with_extra_environ(self, mocked_exec):
|
||||||
err = batchprocessors.BATCHCODE.code_exec(
|
err = batchprocessors.BATCHCODE.code_exec(
|
||||||
'# batchcode code:\n\nprint("Hello")\n',
|
'# batchcode code:\n\nprint("Hello")\n', extra_environ={"foo": "bar", "baz": True}
|
||||||
extra_environ={'foo': 'bar', 'baz': True})
|
)
|
||||||
self.assertIsNone(err)
|
self.assertIsNone(err)
|
||||||
|
|
||||||
@mock.patch('builtins.exec')
|
@mock.patch("builtins.exec")
|
||||||
def test_execs_codeblock_raises(self, mocked_exec):
|
def test_execs_codeblock_raises(self, mocked_exec):
|
||||||
mocked_exec.side_effect = Exception
|
mocked_exec.side_effect = Exception
|
||||||
err = batchprocessors.BATCHCODE.code_exec(
|
err = batchprocessors.BATCHCODE.code_exec(
|
||||||
'# batchcode code:\n\nprint("Hello")\nprint("Evennia")',
|
'# batchcode code:\n\nprint("Hello")\nprint("Evennia")', extra_environ={}
|
||||||
extra_environ={})
|
)
|
||||||
self.assertIsNotNone(err)
|
self.assertIsNotNone(err)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue