mirror of
https://github.com/fergalmoran/picard.git
synced 2025-12-22 09:18:18 +00:00
Move script functions extension point to extension_points
This commit is contained in:
@@ -30,10 +30,14 @@ from picard.plugin import ExtensionPoint
|
||||
|
||||
|
||||
ext_point_formats = ExtensionPoint(label='formats')
|
||||
formats_extensions = {}
|
||||
_formats_extensions = {}
|
||||
|
||||
|
||||
def register_format(file_format):
|
||||
ext_point_formats.register(file_format.__module__, file_format)
|
||||
for ext in file_format.EXTENSIONS:
|
||||
formats_extensions[ext[1:]] = file_format
|
||||
_formats_extensions[ext[1:]] = file_format
|
||||
|
||||
|
||||
def ext_to_format(ext):
|
||||
return _formats_extensions.get(ext, None)
|
||||
|
||||
162
picard/extension_points/script_functions.py
Normal file
162
picard/extension_points/script_functions.py
Normal file
@@ -0,0 +1,162 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Picard, the next-generation MusicBrainz tagger
|
||||
#
|
||||
# Copyright (C) 2006-2009, 2012 Lukáš Lalinský
|
||||
# Copyright (C) 2007 Javier Kohen
|
||||
# Copyright (C) 2008-2011, 2014-2015, 2018-2021, 2023 Philipp Wolfer
|
||||
# Copyright (C) 2009 Carlin Mangar
|
||||
# Copyright (C) 2009 Nikolai Prokoschenko
|
||||
# Copyright (C) 2011-2012 Michael Wiencek
|
||||
# Copyright (C) 2012 Chad Wilson
|
||||
# Copyright (C) 2012 stephen
|
||||
# Copyright (C) 2012, 2014, 2017, 2021 Wieland Hoffmann
|
||||
# Copyright (C) 2013-2014, 2017-2024 Laurent Monin
|
||||
# Copyright (C) 2014, 2017, 2021 Sophist-UK
|
||||
# Copyright (C) 2016-2017 Sambhav Kothari
|
||||
# Copyright (C) 2016-2017 Ville Skyttä
|
||||
# Copyright (C) 2017-2018 Antonio Larrosa
|
||||
# Copyright (C) 2018 Calvin Walton
|
||||
# Copyright (C) 2018 virusMac
|
||||
# Copyright (C) 2020-2023 Bob Swift
|
||||
# Copyright (C) 2021 Adam James
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
from collections import namedtuple
|
||||
from inspect import getfullargspec
|
||||
|
||||
|
||||
try:
|
||||
from markdown import markdown
|
||||
except ImportError:
|
||||
markdown = None
|
||||
|
||||
from picard.i18n import gettext as _
|
||||
from picard.plugin import ExtensionPoint
|
||||
|
||||
|
||||
ext_point_script_functions = ExtensionPoint(label='script_functions')
|
||||
|
||||
|
||||
Bound = namedtuple('Bound', ['lower', 'upper'])
|
||||
|
||||
|
||||
class FunctionRegistryItem:
|
||||
def __init__(self, function, eval_args, argcount, documentation=None,
|
||||
name=None, module=None):
|
||||
self.function = function
|
||||
self.eval_args = eval_args
|
||||
self.argcount = argcount
|
||||
self.documentation = documentation
|
||||
self.name = name
|
||||
self.module = module
|
||||
|
||||
def __repr__(self):
|
||||
return '{classname}({me.function}, {me.eval_args}, {me.argcount}, {doc})'.format(
|
||||
classname=self.__class__.__name__,
|
||||
me=self,
|
||||
doc='"""{0}"""'.format(self.documentation) if self.documentation else None
|
||||
)
|
||||
|
||||
def _postprocess(self, data, postprocessor):
|
||||
if postprocessor is not None:
|
||||
data = postprocessor(data, function=self)
|
||||
return data
|
||||
|
||||
def markdowndoc(self, postprocessor=None):
|
||||
if self.documentation is not None:
|
||||
ret = _(self.documentation)
|
||||
else:
|
||||
ret = ''
|
||||
return self._postprocess(ret, postprocessor)
|
||||
|
||||
def htmldoc(self, postprocessor=None):
|
||||
if markdown is not None:
|
||||
ret = markdown(self.markdowndoc())
|
||||
else:
|
||||
ret = ''
|
||||
return self._postprocess(ret, postprocessor)
|
||||
|
||||
|
||||
def register_script_function(function, name=None, eval_args=True,
|
||||
check_argcount=True, documentation=None):
|
||||
"""Registers a script function. If ``name`` is ``None``,
|
||||
``function.__name__`` will be used.
|
||||
If ``eval_args`` is ``False``, the arguments will not be evaluated before being
|
||||
passed to ``function``.
|
||||
If ``check_argcount`` is ``False`` the number of arguments passed to the
|
||||
function will not be verified."""
|
||||
|
||||
args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, annotations = getfullargspec(function)
|
||||
|
||||
required_kwonlyargs = len(kwonlyargs)
|
||||
if kwonlydefaults is not None:
|
||||
required_kwonlyargs -= len(kwonlydefaults.keys())
|
||||
if required_kwonlyargs:
|
||||
raise TypeError("Functions with required keyword-only parameters are not supported")
|
||||
|
||||
args = len(args) - 1 # -1 for the parser
|
||||
varargs = varargs is not None
|
||||
defaults = len(defaults) if defaults else 0
|
||||
|
||||
argcount = Bound(args - defaults, args if not varargs else None)
|
||||
|
||||
if name is None:
|
||||
name = function.__name__
|
||||
ext_point_script_functions.register(
|
||||
function.__module__,
|
||||
(
|
||||
name,
|
||||
FunctionRegistryItem(
|
||||
function,
|
||||
eval_args,
|
||||
argcount if argcount and check_argcount else False,
|
||||
documentation=documentation,
|
||||
name=name,
|
||||
module=function.__module__,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def script_function(name=None, eval_args=True, check_argcount=True, prefix='func_', documentation=None):
|
||||
"""Decorator helper to register script functions
|
||||
|
||||
It calls ``register_script_function()`` and share same arguments
|
||||
Extra optional arguments:
|
||||
``prefix``: define the prefix to be removed from defined function to name script function
|
||||
By default, ``func_foo`` will create ``foo`` script function
|
||||
|
||||
Example:
|
||||
@script_function(eval_args=False)
|
||||
def func_myscriptfunc():
|
||||
...
|
||||
"""
|
||||
def script_function_decorator(func):
|
||||
fname = func.__name__
|
||||
if name is None and prefix and fname.startswith(prefix):
|
||||
sname = fname[len(prefix):]
|
||||
else:
|
||||
sname = name
|
||||
register_script_function(
|
||||
func,
|
||||
name=sname,
|
||||
eval_args=eval_args,
|
||||
check_argcount=check_argcount,
|
||||
documentation=documentation
|
||||
)
|
||||
return func
|
||||
return script_function_decorator
|
||||
@@ -29,7 +29,7 @@
|
||||
from picard import log
|
||||
from picard.extension_points.formats import (
|
||||
ext_point_formats,
|
||||
formats_extensions,
|
||||
ext_to_format,
|
||||
)
|
||||
|
||||
|
||||
@@ -43,10 +43,6 @@ def supported_extensions():
|
||||
return [ext for exts, name in supported_formats() for ext in exts]
|
||||
|
||||
|
||||
def ext_to_format(ext):
|
||||
return formats_extensions.get(ext, None)
|
||||
|
||||
|
||||
def guess_format(filename, options=None):
|
||||
"""Select the best matching file type amongst supported formats."""
|
||||
if options is None:
|
||||
@@ -79,7 +75,10 @@ def open_(filename):
|
||||
i = filename.rfind(".")
|
||||
if i >= 0:
|
||||
ext = filename[i+1:].lower()
|
||||
audio_file = formats_extensions[ext](filename)
|
||||
file_format = ext_to_format(ext)
|
||||
if file_format is None:
|
||||
return None
|
||||
audio_file = file_format(filename)
|
||||
else:
|
||||
# If there is no extension, try to guess the format based on file headers
|
||||
audio_file = guess_format(filename)
|
||||
|
||||
@@ -41,14 +41,13 @@ from picard.const.defaults import (
|
||||
DEFAULT_FILE_NAMING_FORMAT,
|
||||
DEFAULT_NAMING_PRESET_ID,
|
||||
)
|
||||
from picard.extension_points import script_functions
|
||||
from picard.i18n import (
|
||||
N_,
|
||||
gettext as _,
|
||||
)
|
||||
from picard.script.functions import ( # noqa: F401 # pylint: disable=unused-import
|
||||
register_script_function,
|
||||
script_function,
|
||||
)
|
||||
# Those imports are required to actually parse the code and interpret decorators
|
||||
import picard.script.functions # noqa: F401 # pylint: disable=unused-import
|
||||
from picard.script.parser import ( # noqa: F401 # pylint: disable=unused-import
|
||||
MultiValue,
|
||||
ScriptEndOfFile,
|
||||
@@ -73,7 +72,7 @@ class ScriptFunctionDocError(Exception):
|
||||
|
||||
def script_function_documentation(name, fmt, functions=None, postprocessor=None):
|
||||
if functions is None:
|
||||
functions = dict(ScriptParser._function_registry)
|
||||
functions = dict(script_functions.ext_point_script_functions)
|
||||
if name not in functions:
|
||||
raise ScriptFunctionDocError("no such function: %s (known functions: %r)" % (name, [name for name in functions]))
|
||||
|
||||
@@ -87,13 +86,13 @@ def script_function_documentation(name, fmt, functions=None, postprocessor=None)
|
||||
|
||||
def script_function_names(functions=None):
|
||||
if functions is None:
|
||||
functions = dict(ScriptParser._function_registry)
|
||||
functions = dict(script_functions.ext_point_script_functions)
|
||||
yield from sorted(functions)
|
||||
|
||||
|
||||
def script_function_documentation_all(fmt='markdown', pre='',
|
||||
post='', postprocessor=None):
|
||||
functions = dict(ScriptParser._function_registry)
|
||||
functions = dict(script_functions.ext_point_script_functions)
|
||||
doc_elements = []
|
||||
for name in script_function_names(functions):
|
||||
doc_element = script_function_documentation(name, fmt,
|
||||
|
||||
@@ -39,21 +39,19 @@
|
||||
from collections import namedtuple
|
||||
import datetime
|
||||
from functools import reduce
|
||||
from inspect import getfullargspec
|
||||
import operator
|
||||
import re
|
||||
import unicodedata
|
||||
|
||||
from picard.const.countries import RELEASE_COUNTRIES
|
||||
from picard.extension_points.script_functions import script_function
|
||||
from picard.i18n import (
|
||||
N_,
|
||||
gettext as _,
|
||||
gettext_countries,
|
||||
)
|
||||
from picard.metadata import MULTI_VALUED_JOINER
|
||||
from picard.script.parser import (
|
||||
MultiValue,
|
||||
ScriptParser,
|
||||
ScriptRuntimeError,
|
||||
normalize_tagname,
|
||||
)
|
||||
@@ -63,123 +61,6 @@ from picard.util import (
|
||||
)
|
||||
|
||||
|
||||
try:
|
||||
from markdown import markdown
|
||||
except ImportError:
|
||||
markdown = None
|
||||
|
||||
|
||||
Bound = namedtuple('Bound', ['lower', 'upper'])
|
||||
|
||||
|
||||
class FunctionRegistryItem:
|
||||
def __init__(self, function, eval_args, argcount, documentation=None,
|
||||
name=None, module=None):
|
||||
self.function = function
|
||||
self.eval_args = eval_args
|
||||
self.argcount = argcount
|
||||
self.documentation = documentation
|
||||
self.name = name
|
||||
self.module = module
|
||||
|
||||
def __repr__(self):
|
||||
return '{classname}({me.function}, {me.eval_args}, {me.argcount}, {doc})'.format(
|
||||
classname=self.__class__.__name__,
|
||||
me=self,
|
||||
doc='"""{0}"""'.format(self.documentation) if self.documentation else None
|
||||
)
|
||||
|
||||
def _postprocess(self, data, postprocessor):
|
||||
if postprocessor is not None:
|
||||
data = postprocessor(data, function=self)
|
||||
return data
|
||||
|
||||
def markdowndoc(self, postprocessor=None):
|
||||
if self.documentation is not None:
|
||||
ret = _(self.documentation)
|
||||
else:
|
||||
ret = ''
|
||||
return self._postprocess(ret, postprocessor)
|
||||
|
||||
def htmldoc(self, postprocessor=None):
|
||||
if markdown is not None:
|
||||
ret = markdown(self.markdowndoc())
|
||||
else:
|
||||
ret = ''
|
||||
return self._postprocess(ret, postprocessor)
|
||||
|
||||
|
||||
def register_script_function(function, name=None, eval_args=True,
|
||||
check_argcount=True, documentation=None):
|
||||
"""Registers a script function. If ``name`` is ``None``,
|
||||
``function.__name__`` will be used.
|
||||
If ``eval_args`` is ``False``, the arguments will not be evaluated before being
|
||||
passed to ``function``.
|
||||
If ``check_argcount`` is ``False`` the number of arguments passed to the
|
||||
function will not be verified."""
|
||||
|
||||
args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, annotations = getfullargspec(function)
|
||||
|
||||
required_kwonlyargs = len(kwonlyargs)
|
||||
if kwonlydefaults is not None:
|
||||
required_kwonlyargs -= len(kwonlydefaults.keys())
|
||||
if required_kwonlyargs:
|
||||
raise TypeError("Functions with required keyword-only parameters are not supported")
|
||||
|
||||
args = len(args) - 1 # -1 for the parser
|
||||
varargs = varargs is not None
|
||||
defaults = len(defaults) if defaults else 0
|
||||
|
||||
argcount = Bound(args - defaults, args if not varargs else None)
|
||||
|
||||
if name is None:
|
||||
name = function.__name__
|
||||
ScriptParser._function_registry.register(
|
||||
function.__module__,
|
||||
(
|
||||
name,
|
||||
FunctionRegistryItem(
|
||||
function,
|
||||
eval_args,
|
||||
argcount if argcount and check_argcount else False,
|
||||
documentation=documentation,
|
||||
name=name,
|
||||
module=function.__module__,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def script_function(name=None, eval_args=True, check_argcount=True, prefix='func_', documentation=None):
|
||||
"""Decorator helper to register script functions
|
||||
|
||||
It calls ``register_script_function()`` and share same arguments
|
||||
Extra optional arguments:
|
||||
``prefix``: define the prefix to be removed from defined function to name script function
|
||||
By default, ``func_foo`` will create ``foo`` script function
|
||||
|
||||
Example:
|
||||
@script_function(eval_args=False)
|
||||
def func_myscriptfunc():
|
||||
...
|
||||
"""
|
||||
def script_function_decorator(func):
|
||||
fname = func.__name__
|
||||
if name is None and prefix and fname.startswith(prefix):
|
||||
sname = fname[len(prefix):]
|
||||
else:
|
||||
sname = name
|
||||
register_script_function(
|
||||
func,
|
||||
name=sname,
|
||||
eval_args=eval_args,
|
||||
check_argcount=check_argcount,
|
||||
documentation=documentation
|
||||
)
|
||||
return func
|
||||
return script_function_decorator
|
||||
|
||||
|
||||
def _compute_int(operation, *args):
|
||||
return str(reduce(operation, map(int, args)))
|
||||
|
||||
|
||||
@@ -38,11 +38,11 @@
|
||||
from collections.abc import MutableSequence
|
||||
from queue import LifoQueue
|
||||
|
||||
from picard.extension_points import script_functions
|
||||
from picard.metadata import (
|
||||
MULTI_VALUED_JOINER,
|
||||
Metadata,
|
||||
)
|
||||
from picard.plugin import ExtensionPoint
|
||||
|
||||
|
||||
class ScriptError(Exception):
|
||||
@@ -216,7 +216,6 @@ Grammar:
|
||||
argument ::= (variable | function | argtext)*
|
||||
"""
|
||||
|
||||
_function_registry = ExtensionPoint(label='function_registry')
|
||||
_cache = {}
|
||||
|
||||
def __init__(self):
|
||||
@@ -362,9 +361,7 @@ Grammar:
|
||||
return (tokens, ch)
|
||||
|
||||
def load_functions(self):
|
||||
self.functions = {}
|
||||
for name, item in ScriptParser._function_registry:
|
||||
self.functions[name] = item
|
||||
self.functions = dict(script_functions.ext_point_script_functions)
|
||||
|
||||
def parse(self, script, functions=False):
|
||||
"""Parse the script."""
|
||||
|
||||
@@ -43,6 +43,11 @@ from test.picardtestcase import PicardTestCase
|
||||
|
||||
from picard.cluster import Cluster
|
||||
from picard.const.defaults import DEFAULT_FILE_NAMING_FORMAT
|
||||
from picard.extension_points.script_functions import (
|
||||
FunctionRegistryItem,
|
||||
register_script_function,
|
||||
script_function,
|
||||
)
|
||||
from picard.metadata import (
|
||||
MULTI_VALUED_JOINER,
|
||||
Metadata,
|
||||
@@ -60,12 +65,9 @@ from picard.script import (
|
||||
ScriptSyntaxError,
|
||||
ScriptUnicodeError,
|
||||
ScriptUnknownFunction,
|
||||
register_script_function,
|
||||
script_function,
|
||||
script_function_documentation,
|
||||
script_function_documentation_all,
|
||||
)
|
||||
from picard.script.functions import FunctionRegistryItem
|
||||
|
||||
|
||||
try:
|
||||
@@ -170,77 +172,77 @@ class ScriptParserTest(PicardTestCase):
|
||||
with self.assertRaisesRegex(ScriptUnicodeError, areg):
|
||||
self.parser.eval("\\ufffg")
|
||||
|
||||
@patch('picard.extension_points.script_functions.ext_point_script_functions', ExtensionPoint(label='test_script'))
|
||||
def test_script_function_decorator_default(self):
|
||||
# test default decorator and default prefix
|
||||
with patch.object(ScriptParser, '_function_registry', ExtensionPoint()):
|
||||
@script_function()
|
||||
def func_somefunc(parser):
|
||||
return "x"
|
||||
self.assertScriptResultEquals("$somefunc()", "x")
|
||||
@script_function()
|
||||
def func_somefunc(parser):
|
||||
return "x"
|
||||
self.assertScriptResultEquals("$somefunc()", "x")
|
||||
|
||||
@patch('picard.extension_points.script_functions.ext_point_script_functions', ExtensionPoint(label='test_script'))
|
||||
def test_script_function_decorator_no_prefix(self):
|
||||
# function without prefix
|
||||
with patch.object(ScriptParser, '_function_registry', ExtensionPoint()):
|
||||
@script_function()
|
||||
def somefunc(parser):
|
||||
return "x"
|
||||
self.assertScriptResultEquals("$somefunc()", "x")
|
||||
@script_function()
|
||||
def somefunc(parser):
|
||||
return "x"
|
||||
self.assertScriptResultEquals("$somefunc()", "x")
|
||||
|
||||
@patch('picard.extension_points.script_functions.ext_point_script_functions', ExtensionPoint(label='test_script'))
|
||||
def test_script_function_decorator_arg(self):
|
||||
# function with argument
|
||||
with patch.object(ScriptParser, '_function_registry', ExtensionPoint()):
|
||||
@script_function()
|
||||
def somefunc(parser, arg):
|
||||
return arg
|
||||
@script_function()
|
||||
def somefunc(parser, arg):
|
||||
return arg
|
||||
|
||||
@script_function()
|
||||
def title(parser, arg):
|
||||
return arg.upper()
|
||||
@script_function()
|
||||
def title(parser, arg):
|
||||
return arg.upper()
|
||||
|
||||
self.assertScriptResultEquals("$somefunc($title(x))", "X")
|
||||
areg = r"^\d+:\d+:\$somefunc: Wrong number of arguments for \$somefunc: Expected exactly 1"
|
||||
with self.assertRaisesRegex(ScriptError, areg):
|
||||
self.parser.eval("$somefunc()")
|
||||
self.assertScriptResultEquals("$somefunc($title(x))", "X")
|
||||
areg = r"^\d+:\d+:\$somefunc: Wrong number of arguments for \$somefunc: Expected exactly 1"
|
||||
with self.assertRaisesRegex(ScriptError, areg):
|
||||
self.parser.eval("$somefunc()")
|
||||
|
||||
@patch('picard.extension_points.script_functions.ext_point_script_functions', ExtensionPoint(label='test_script'))
|
||||
def test_script_function_decorator_argcount(self):
|
||||
# ignore argument count
|
||||
with patch.object(ScriptParser, '_function_registry', ExtensionPoint()):
|
||||
@script_function(check_argcount=False)
|
||||
def somefunc(parser, *arg):
|
||||
return str(len(arg))
|
||||
self.assertScriptResultEquals("$somefunc(a,b,c)", "3")
|
||||
@script_function(check_argcount=False)
|
||||
def somefunc(parser, *arg):
|
||||
return str(len(arg))
|
||||
self.assertScriptResultEquals("$somefunc(a,b,c)", "3")
|
||||
|
||||
@patch('picard.extension_points.script_functions.ext_point_script_functions', ExtensionPoint(label='test_script'))
|
||||
def test_script_function_decorator_altname(self):
|
||||
# alternative name
|
||||
with patch.object(ScriptParser, '_function_registry', ExtensionPoint()):
|
||||
@script_function(name="otherfunc")
|
||||
def somefunc4(parser):
|
||||
return "x"
|
||||
self.assertScriptResultEquals("$otherfunc()", "x")
|
||||
areg = r"^\d+:\d+:\$somefunc: Unknown function '\$somefunc'"
|
||||
with self.assertRaisesRegex(ScriptError, areg):
|
||||
self.parser.eval("$somefunc()")
|
||||
@script_function(name="otherfunc")
|
||||
def somefunc4(parser):
|
||||
return "x"
|
||||
self.assertScriptResultEquals("$otherfunc()", "x")
|
||||
areg = r"^\d+:\d+:\$somefunc: Unknown function '\$somefunc'"
|
||||
with self.assertRaisesRegex(ScriptError, areg):
|
||||
self.parser.eval("$somefunc()")
|
||||
|
||||
@patch('picard.extension_points.script_functions.ext_point_script_functions', ExtensionPoint(label='test_script'))
|
||||
def test_script_function_decorator_altprefix(self):
|
||||
# alternative prefix
|
||||
with patch.object(ScriptParser, '_function_registry', ExtensionPoint()):
|
||||
@script_function(prefix='theprefix_')
|
||||
def theprefix_somefunc(parser):
|
||||
return "x"
|
||||
self.assertScriptResultEquals("$somefunc()", "x")
|
||||
@script_function(prefix='theprefix_')
|
||||
def theprefix_somefunc(parser):
|
||||
return "x"
|
||||
self.assertScriptResultEquals("$somefunc()", "x")
|
||||
|
||||
@patch('picard.extension_points.script_functions.ext_point_script_functions', ExtensionPoint(label='test_script'))
|
||||
def test_script_function_decorator_eval_args(self):
|
||||
# disable argument evaluation
|
||||
with patch.object(ScriptParser, '_function_registry', ExtensionPoint()):
|
||||
@script_function(eval_args=False)
|
||||
def somefunc(parser, arg):
|
||||
return arg.eval(parser)
|
||||
@script_function(eval_args=False)
|
||||
def somefunc(parser, arg):
|
||||
return arg.eval(parser)
|
||||
|
||||
@script_function()
|
||||
def title(parser, arg):
|
||||
return arg.upper()
|
||||
@script_function()
|
||||
def title(parser, arg):
|
||||
return arg.upper()
|
||||
|
||||
self.assertScriptResultEquals("$somefunc($title(x))", "X")
|
||||
self.assertScriptResultEquals("$somefunc($title(x))", "X")
|
||||
|
||||
@staticmethod
|
||||
def assertStartswith(text, expect):
|
||||
@@ -252,98 +254,95 @@ class ScriptParserTest(PicardTestCase):
|
||||
if not text.endswith(expect):
|
||||
raise AssertionError("do not end with %r but with %r" % (expect, text[-len(expect):]))
|
||||
|
||||
@patch('picard.extension_points.script_functions.ext_point_script_functions', ExtensionPoint(label='test_script'))
|
||||
def test_script_function_documentation_nodoc(self):
|
||||
"""test script_function_documentation() with a function without documentation"""
|
||||
with patch.object(ScriptParser, '_function_registry', ExtensionPoint()):
|
||||
@script_function()
|
||||
def func_nodocfunc(parser):
|
||||
return ""
|
||||
|
||||
@script_function()
|
||||
def func_nodocfunc(parser):
|
||||
return ""
|
||||
|
||||
doc = script_function_documentation('nodocfunc', 'markdown')
|
||||
self.assertEqual(doc, '')
|
||||
doc = script_function_documentation('nodocfunc', 'html')
|
||||
self.assertEqual(doc, '')
|
||||
doc = script_function_documentation('nodocfunc', 'markdown')
|
||||
self.assertEqual(doc, '')
|
||||
doc = script_function_documentation('nodocfunc', 'html')
|
||||
self.assertEqual(doc, '')
|
||||
|
||||
@patch('picard.extension_points.script_functions.ext_point_script_functions', ExtensionPoint(label='test_script'))
|
||||
def test_script_function_documentation(self):
|
||||
"""test script_function_documentation() with a function with documentation"""
|
||||
with patch.object(ScriptParser, '_function_registry', ExtensionPoint()):
|
||||
# the documentation used to test includes backquotes
|
||||
testdoc = '`$somefunc()`'
|
||||
# the documentation used to test includes backquotes
|
||||
testdoc = '`$somefunc()`'
|
||||
|
||||
@script_function(documentation=testdoc)
|
||||
def func_somefunc(parser):
|
||||
return "x"
|
||||
@script_function(documentation=testdoc)
|
||||
def func_somefunc(parser):
|
||||
return "x"
|
||||
|
||||
doc = script_function_documentation('somefunc', 'markdown')
|
||||
self.assertEqual(doc, testdoc)
|
||||
areg = r"^no such documentation format: unknownformat"
|
||||
with self.assertRaisesRegex(ScriptFunctionDocError, areg):
|
||||
script_function_documentation('somefunc', 'unknownformat')
|
||||
doc = script_function_documentation('somefunc', 'markdown')
|
||||
self.assertEqual(doc, testdoc)
|
||||
areg = r"^no such documentation format: unknownformat"
|
||||
with self.assertRaisesRegex(ScriptFunctionDocError, areg):
|
||||
script_function_documentation('somefunc', 'unknownformat')
|
||||
|
||||
@unittest.skipUnless(markdown, "markdown module missing")
|
||||
@patch('picard.extension_points.script_functions.ext_point_script_functions', ExtensionPoint(label='test_script'))
|
||||
def test_script_function_documentation_html(self):
|
||||
"""test script_function_documentation() with a function with documentation"""
|
||||
with patch.object(ScriptParser, '_function_registry', ExtensionPoint()):
|
||||
# get html code as generated by markdown
|
||||
pre, post = markdown('`XXX`').split('XXX')
|
||||
# get html code as generated by markdown
|
||||
pre, post = markdown('`XXX`').split('XXX')
|
||||
|
||||
# the documentation used to test includes backquotes
|
||||
testdoc = '`$somefunc()`'
|
||||
# the documentation used to test includes backquotes
|
||||
testdoc = '`$somefunc()`'
|
||||
|
||||
@script_function(documentation=testdoc)
|
||||
def func_somefunc(parser):
|
||||
return "x"
|
||||
@script_function(documentation=testdoc)
|
||||
def func_somefunc(parser):
|
||||
return "x"
|
||||
|
||||
doc = script_function_documentation('somefunc', 'html')
|
||||
self.assertEqual(doc, pre + '$somefunc()' + post)
|
||||
doc = script_function_documentation('somefunc', 'html')
|
||||
self.assertEqual(doc, pre + '$somefunc()' + post)
|
||||
|
||||
@patch('picard.extension_points.script_functions.ext_point_script_functions', ExtensionPoint(label='test_script'))
|
||||
def test_script_function_documentation_unknown_function(self):
|
||||
"""test script_function_documentation() with an unknown function"""
|
||||
with patch.object(ScriptParser, '_function_registry', ExtensionPoint()):
|
||||
areg = r"^no such function: unknownfunc"
|
||||
with self.assertRaisesRegex(ScriptFunctionDocError, areg):
|
||||
script_function_documentation('unknownfunc', 'html')
|
||||
areg = r"^no such function: unknownfunc"
|
||||
with self.assertRaisesRegex(ScriptFunctionDocError, areg):
|
||||
script_function_documentation('unknownfunc', 'html')
|
||||
|
||||
@patch('picard.extension_points.script_functions.ext_point_script_functions', ExtensionPoint(label='test_script'))
|
||||
def test_script_function_documentation_all(self):
|
||||
"""test script_function_documentation_all() with markdown format"""
|
||||
with patch.object(ScriptParser, '_function_registry', ExtensionPoint()):
|
||||
@script_function(documentation='somedoc2')
|
||||
def func_somefunc2(parser):
|
||||
return "x"
|
||||
|
||||
@script_function(documentation='somedoc2')
|
||||
def func_somefunc2(parser):
|
||||
return "x"
|
||||
@script_function(documentation='somedoc1')
|
||||
def func_somefunc1(parser):
|
||||
return "x"
|
||||
|
||||
@script_function(documentation='somedoc1')
|
||||
def func_somefunc1(parser):
|
||||
return "x"
|
||||
|
||||
docall = script_function_documentation_all()
|
||||
self.assertEqual(docall, 'somedoc1\nsomedoc2')
|
||||
docall = script_function_documentation_all()
|
||||
self.assertEqual(docall, 'somedoc1\nsomedoc2')
|
||||
|
||||
@unittest.skipUnless(markdown, "markdown module missing")
|
||||
@patch('picard.extension_points.script_functions.ext_point_script_functions', ExtensionPoint(label='test_script'))
|
||||
def test_script_function_documentation_all_html(self):
|
||||
"""test script_function_documentation_all() with html format"""
|
||||
with patch.object(ScriptParser, '_function_registry', ExtensionPoint()):
|
||||
# get html code as generated by markdown
|
||||
pre, post = markdown('XXX').split('XXX')
|
||||
|
||||
# get html code as generated by markdown
|
||||
pre, post = markdown('XXX').split('XXX')
|
||||
@script_function(documentation='somedoc')
|
||||
def func_somefunc(parser):
|
||||
return "x"
|
||||
|
||||
@script_function(documentation='somedoc')
|
||||
def func_somefunc(parser):
|
||||
return "x"
|
||||
def postprocessor(data, function):
|
||||
return 'w' + data + function.name + 'y'
|
||||
|
||||
def postprocessor(data, function):
|
||||
return 'w' + data + function.name + 'y'
|
||||
docall = script_function_documentation_all(
|
||||
fmt='html',
|
||||
pre='<div id="test">',
|
||||
post="</div>\n",
|
||||
postprocessor=postprocessor,
|
||||
)
|
||||
|
||||
docall = script_function_documentation_all(
|
||||
fmt='html',
|
||||
pre='<div id="test">',
|
||||
post="</div>\n",
|
||||
postprocessor=postprocessor,
|
||||
)
|
||||
|
||||
self.assertStartswith(docall, '<div id="test">w' + pre)
|
||||
self.assertEndswith(docall, post + 'somefuncy</div>\n')
|
||||
self.assertStartswith(docall, '<div id="test">w' + pre)
|
||||
self.assertEndswith(docall, post + 'somefuncy</div>\n')
|
||||
|
||||
def test_unknown_function(self):
|
||||
areg = r"^\d+:\d+:\$unknownfunction: Unknown function '\$unknownfunction'"
|
||||
|
||||
@@ -26,9 +26,9 @@ from test.picardtestcase import PicardTestCase
|
||||
|
||||
from picard import config
|
||||
from picard.const.sys import IS_WIN
|
||||
from picard.extension_points.script_functions import register_script_function
|
||||
from picard.file import File
|
||||
from picard.metadata import Metadata
|
||||
from picard.script import register_script_function
|
||||
from picard.util.scripttofilename import (
|
||||
script_to_filename,
|
||||
script_to_filename_with_metadata,
|
||||
|
||||
Reference in New Issue
Block a user