Merge pull request #710 from Sophist-UK/PICARD-922_improve-inmulti-as-per-1.4.1

PICARD-922: Make multi-value script functions work correctly
This commit is contained in:
Laurent Monin
2017-05-11 11:05:34 +02:00
committed by GitHub
2 changed files with 118 additions and 16 deletions

View File

@@ -28,6 +28,7 @@ from inspect import getfullargspec
from picard.metadata import Metadata
from picard.metadata import MULTI_VALUED_JOINER
from picard.plugin import ExtensionPoint
from picard.util import uniqify
class ScriptError(Exception):
@@ -324,6 +325,31 @@ def _compute_logic(operation, *args):
return operation(args)
def _get_multi_values(parser, multi, separator):
if isinstance(separator, ScriptExpression):
separator = separator.eval(parser)
if separator == MULTI_VALUED_JOINER:
# Convert ScriptExpression containing only a single variable into variable
if (isinstance(multi, ScriptExpression) and
len(multi) == 1 and
isinstance(multi[0], ScriptVariable)):
multi = multi[0]
# If a variable, return multi-values
if isinstance(multi, ScriptVariable):
if multi.name.startswith("_"):
name = "~" + multi.name[1:]
else:
name = multi.name
return parser.context.getall(name)
# Fall-back to converting to a string and splitting if haystack is an expression
# or user has overridden the separator character.
multi = multi.eval(parser)
return multi.split(separator) if separator else [multi]
def func_if(parser, _if, _then, _else=None):
"""If ``if`` is not empty, it returns ``then``, otherwise it returns ``else``."""
if _if.eval(parser):
@@ -395,9 +421,14 @@ def func_in(parser, text, needle):
return ""
def func_inmulti(parser, text, value, separator=MULTI_VALUED_JOINER):
"""Splits ``text`` by ``separator``, and returns true if the resulting list contains ``value``."""
return func_in(parser, text.split(separator) if separator else [text], value)
def func_inmulti(parser, haystack, needle, separator=MULTI_VALUED_JOINER):
"""Searches for ``needle`` in ``haystack``, supporting a list variable for
``haystack``. If a string is used instead, then a ``separator`` can be
used to split it. In both cases, it returns true if the resulting list
contains exactly ``needle`` as a member."""
needle = needle.eval(parser)
return func_in(parser, _get_multi_values(parser, haystack, separator), needle)
def func_rreplace(parser, text, old, new):
@@ -486,7 +517,7 @@ def func_copymerge(parser, new, old):
old = "~" + old[1:]
newvals = parser.context.getall(new)
oldvals = parser.context.getall(old)
parser.context[new] = newvals + list(set(oldvals) - set(newvals))
parser.context[new] = uniqify(newvals + oldvals)
return ""
@@ -644,6 +675,10 @@ def func_len(parser, text=""):
return string_(len(text))
def func_lenmulti(parser, multi, separator=MULTI_VALUED_JOINER):
return func_len(parser, _get_multi_values(parser, multi, separator))
def func_performer(parser, pattern="", join=", "):
values = []
for name, value in parser.context.items():
@@ -827,10 +862,11 @@ register_script_function(func_lte, "lte")
register_script_function(func_gt, "gt")
register_script_function(func_gte, "gte")
register_script_function(func_in, "in")
register_script_function(func_inmulti, "inmulti")
register_script_function(func_inmulti, "inmulti", eval_args=False)
register_script_function(func_copy, "copy")
register_script_function(func_copymerge, "copymerge")
register_script_function(func_len, "len")
register_script_function(func_lenmulti, "lenmulti", eval_args=False)
register_script_function(func_performer, "performer")
register_script_function(func_matchedtracks, "matchedtracks")
register_script_function(func_is_complete, "is_complete")

View File

@@ -11,9 +11,12 @@ class ScriptParserTest(unittest.TestCase):
config.setting = {
'enabled_plugins': '',
}
self.parser = ScriptParser()
def func_noargstest(parser):
return ""
register_script_function(func_noargstest, "noargstest")
def assertScriptResultEquals(self, script, expected, context=None):
@@ -172,16 +175,10 @@ class ScriptParserTest(unittest.TestCase):
self.assertScriptResultEquals("$upper(AbeCeDA)", "ABECEDA")
def test_cmd_rreplace(self):
self.assertEqual(
self.parser.eval(r'''$rreplace(test \(disc 1\),\\s\\\(disc \\d+\\\),)'''),
"test"
)
self.assertScriptResultEquals(r'''$rreplace(test \(disc 1\),\\s\\\(disc \\d+\\\),)''', "test")
def test_cmd_rsearch(self):
self.assertEqual(
self.parser.eval(r"$rsearch(test \(disc 1\),\\\(disc \(\\d+\)\\\))"),
"1"
)
self.assertScriptResultEquals(r"$rsearch(test \(disc 1\),\\\(disc \(\\d+\)\\\))", "1")
def test_arguments(self):
self.assertTrue(
@@ -271,7 +268,7 @@ class ScriptParserTest(unittest.TestCase):
def _eval_and_check_copymerge(self, context, expected):
self.parser.eval("$copymerge(target,source)", context)
self.assertEqual(sorted(self.parser.context.getall("target")), sorted(expected))
self.assertEqual(self.parser.context.getall("target"), expected)
def test_cmd_copymerge_notarget(self):
context = Metadata()
@@ -287,8 +284,8 @@ class ScriptParserTest(unittest.TestCase):
def test_cmd_copymerge_removedupes(self):
context = Metadata()
context["target"] = ["tag1", "tag2"]
context["source"] = ["tag2", "tag3"]
context["target"] = ["tag1", "tag2", "tag1"]
context["source"] = ["tag2", "tag3", "tag2"]
self._eval_and_check_copymerge(context, ["tag1", "tag2", "tag3"])
def test_cmd_copymerge_nonlist(self):
@@ -385,6 +382,75 @@ class ScriptParserTest(unittest.TestCase):
self.assertNotIn('performer:bar', context)
self.assertNotIn('performer:foo', context)
def test_cmd_inmulti(self):
context = Metadata()
# Test with single-value string
context["foo"] = "First:A; Second:B; Third:C"
# Tests with $in for comparison purposes
self.assertScriptResultEquals("$in(%foo%,Second:B)", "1", context)
self.assertScriptResultEquals("$in(%foo%,irst:A; Second:B; Thi)", "1", context)
self.assertScriptResultEquals("$in(%foo%,First:A; Second:B; Third:C)", "1", context)
# Base $inmulti tests
self.assertScriptResultEquals("$inmulti(%foo%,Second:B)", "", context)
self.assertScriptResultEquals("$inmulti(%foo%,irst:A; Second:B; Thi)", "", context)
self.assertScriptResultEquals("$inmulti(%foo%,First:A; Second:B; Third:C)", "1", context)
# Test separator override but with existing separator - results should be same as base
self.assertScriptResultEquals("$inmulti(%foo%,Second:B,; )", "", context)
self.assertScriptResultEquals("$inmulti(%foo%,irst:A; Second:B; Thi,; )", "", context)
self.assertScriptResultEquals("$inmulti(%foo%,First:A; Second:B; Third:C,; )", "1", context)
# Test separator override
self.assertScriptResultEquals("$inmulti(%foo%,First:A,:)", "", context)
self.assertScriptResultEquals("$inmulti(%foo%,Second:B,:)", "", context)
self.assertScriptResultEquals("$inmulti(%foo%,Third:C,:)", "", context)
self.assertScriptResultEquals("$inmulti(%foo%,First,:)", "1", context)
self.assertScriptResultEquals("$inmulti(%foo%,A; Second,:)", "1", context)
self.assertScriptResultEquals("$inmulti(%foo%,B; Third,:)", "1", context)
self.assertScriptResultEquals("$inmulti(%foo%,C,:)", "1", context)
# Test with multi-values
context["foo"] = ["First:A", "Second:B", "Third:C"]
# Tests with $in for comparison purposes
self.assertScriptResultEquals("$in(%foo%,Second:B)", "1", context)
self.assertScriptResultEquals("$in(%foo%,irst:A; Second:B; Thi)", "1", context)
self.assertScriptResultEquals("$in(%foo%,First:A; Second:B; Third:C)", "1", context)
# Base $inmulti tests
self.assertScriptResultEquals("$inmulti(%foo%,Second:B)", "1", context)
self.assertScriptResultEquals("$inmulti(%foo%,irst:A; Second:B; Thi)", "", context)
self.assertScriptResultEquals("$inmulti(%foo%,First:A; Second:B; Third:C)", "", context)
# Test separator override but with existing separator - results should be same as base
self.assertScriptResultEquals("$inmulti(%foo%,Second:B,; )", "1", context)
self.assertScriptResultEquals("$inmulti(%foo%,irst:A; Second:B; Thi,; )", "", context)
self.assertScriptResultEquals("$inmulti(%foo%,First:A; Second:B; Third:C,; )", "", context)
# Test separator override
self.assertScriptResultEquals("$inmulti(%foo%,First:A,:)", "", context)
self.assertScriptResultEquals("$inmulti(%foo%,Second:B,:)", "", context)
self.assertScriptResultEquals("$inmulti(%foo%,Third:C,:)", "", context)
self.assertScriptResultEquals("$inmulti(%foo%,First,:)", "1", context)
self.assertScriptResultEquals("$inmulti(%foo%,A; Second,:)", "1", context)
self.assertScriptResultEquals("$inmulti(%foo%,B; Third,:)", "1", context)
self.assertScriptResultEquals("$inmulti(%foo%,C,:)", "1", context)
def test_cmd_lenmulti(self):
context = Metadata()
context["foo"] = "First:A; Second:B; Third:C"
context["bar"] = ["First:A", "Second:B", "Third:C"]
# Tests with $len for comparison purposes
self.assertScriptResultEquals("$len(%foo%)", "26", context)
self.assertScriptResultEquals("$len(%bar%)", "26", context)
# Base $lenmulti tests
self.assertScriptResultEquals("$lenmulti(%foo%)", "1", context)
self.assertScriptResultEquals("$lenmulti(%bar%)", "3", context)
self.assertScriptResultEquals("$lenmulti(%foo%.)", "3", context)
# Test separator override but with existing separator - results should be same as base
self.assertScriptResultEquals("$lenmulti(%foo%,; )", "1", context)
self.assertScriptResultEquals("$lenmulti(%bar%,; )", "3", context)
self.assertScriptResultEquals("$lenmulti(%foo%.,; )", "3", context)
# Test separator override
self.assertScriptResultEquals("$lenmulti(%foo%,:)", "4", context)
self.assertScriptResultEquals("$lenmulti(%bar%,:)", "4", context)
self.assertScriptResultEquals("$lenmulti(%foo%.,:)", "4", context)
def test_required_kwonly_parameters(self):
def func(a, *, required_kwarg):
pass