Move script functions extension point to extension_points

This commit is contained in:
Laurent Monin
2024-05-09 19:47:50 +02:00
parent 52c01f36d3
commit cbfda11da6
8 changed files with 293 additions and 252 deletions

View File

@@ -30,10 +30,14 @@ from picard.plugin import ExtensionPoint
ext_point_formats = ExtensionPoint(label='formats') ext_point_formats = ExtensionPoint(label='formats')
formats_extensions = {} _formats_extensions = {}
def register_format(file_format): def register_format(file_format):
ext_point_formats.register(file_format.__module__, file_format) ext_point_formats.register(file_format.__module__, file_format)
for ext in file_format.EXTENSIONS: 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)

View 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

View File

@@ -29,7 +29,7 @@
from picard import log from picard import log
from picard.extension_points.formats import ( from picard.extension_points.formats import (
ext_point_formats, 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] 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): def guess_format(filename, options=None):
"""Select the best matching file type amongst supported formats.""" """Select the best matching file type amongst supported formats."""
if options is None: if options is None:
@@ -79,7 +75,10 @@ def open_(filename):
i = filename.rfind(".") i = filename.rfind(".")
if i >= 0: if i >= 0:
ext = filename[i+1:].lower() 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: else:
# If there is no extension, try to guess the format based on file headers # If there is no extension, try to guess the format based on file headers
audio_file = guess_format(filename) audio_file = guess_format(filename)

View File

@@ -41,14 +41,13 @@ from picard.const.defaults import (
DEFAULT_FILE_NAMING_FORMAT, DEFAULT_FILE_NAMING_FORMAT,
DEFAULT_NAMING_PRESET_ID, DEFAULT_NAMING_PRESET_ID,
) )
from picard.extension_points import script_functions
from picard.i18n import ( from picard.i18n import (
N_, N_,
gettext as _, gettext as _,
) )
from picard.script.functions import ( # noqa: F401 # pylint: disable=unused-import # Those imports are required to actually parse the code and interpret decorators
register_script_function, import picard.script.functions # noqa: F401 # pylint: disable=unused-import
script_function,
)
from picard.script.parser import ( # noqa: F401 # pylint: disable=unused-import from picard.script.parser import ( # noqa: F401 # pylint: disable=unused-import
MultiValue, MultiValue,
ScriptEndOfFile, ScriptEndOfFile,
@@ -73,7 +72,7 @@ class ScriptFunctionDocError(Exception):
def script_function_documentation(name, fmt, functions=None, postprocessor=None): def script_function_documentation(name, fmt, functions=None, postprocessor=None):
if functions is None: if functions is None:
functions = dict(ScriptParser._function_registry) functions = dict(script_functions.ext_point_script_functions)
if name not in functions: if name not in functions:
raise ScriptFunctionDocError("no such function: %s (known functions: %r)" % (name, [name for name 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): def script_function_names(functions=None):
if functions is None: if functions is None:
functions = dict(ScriptParser._function_registry) functions = dict(script_functions.ext_point_script_functions)
yield from sorted(functions) yield from sorted(functions)
def script_function_documentation_all(fmt='markdown', pre='', def script_function_documentation_all(fmt='markdown', pre='',
post='', postprocessor=None): post='', postprocessor=None):
functions = dict(ScriptParser._function_registry) functions = dict(script_functions.ext_point_script_functions)
doc_elements = [] doc_elements = []
for name in script_function_names(functions): for name in script_function_names(functions):
doc_element = script_function_documentation(name, fmt, doc_element = script_function_documentation(name, fmt,

View File

@@ -39,21 +39,19 @@
from collections import namedtuple from collections import namedtuple
import datetime import datetime
from functools import reduce from functools import reduce
from inspect import getfullargspec
import operator import operator
import re import re
import unicodedata import unicodedata
from picard.const.countries import RELEASE_COUNTRIES from picard.const.countries import RELEASE_COUNTRIES
from picard.extension_points.script_functions import script_function
from picard.i18n import ( from picard.i18n import (
N_, N_,
gettext as _,
gettext_countries, gettext_countries,
) )
from picard.metadata import MULTI_VALUED_JOINER from picard.metadata import MULTI_VALUED_JOINER
from picard.script.parser import ( from picard.script.parser import (
MultiValue, MultiValue,
ScriptParser,
ScriptRuntimeError, ScriptRuntimeError,
normalize_tagname, 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): def _compute_int(operation, *args):
return str(reduce(operation, map(int, args))) return str(reduce(operation, map(int, args)))

View File

@@ -38,11 +38,11 @@
from collections.abc import MutableSequence from collections.abc import MutableSequence
from queue import LifoQueue from queue import LifoQueue
from picard.extension_points import script_functions
from picard.metadata import ( from picard.metadata import (
MULTI_VALUED_JOINER, MULTI_VALUED_JOINER,
Metadata, Metadata,
) )
from picard.plugin import ExtensionPoint
class ScriptError(Exception): class ScriptError(Exception):
@@ -216,7 +216,6 @@ Grammar:
argument ::= (variable | function | argtext)* argument ::= (variable | function | argtext)*
""" """
_function_registry = ExtensionPoint(label='function_registry')
_cache = {} _cache = {}
def __init__(self): def __init__(self):
@@ -362,9 +361,7 @@ Grammar:
return (tokens, ch) return (tokens, ch)
def load_functions(self): def load_functions(self):
self.functions = {} self.functions = dict(script_functions.ext_point_script_functions)
for name, item in ScriptParser._function_registry:
self.functions[name] = item
def parse(self, script, functions=False): def parse(self, script, functions=False):
"""Parse the script.""" """Parse the script."""

View File

@@ -43,6 +43,11 @@ from test.picardtestcase import PicardTestCase
from picard.cluster import Cluster from picard.cluster import Cluster
from picard.const.defaults import DEFAULT_FILE_NAMING_FORMAT 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 ( from picard.metadata import (
MULTI_VALUED_JOINER, MULTI_VALUED_JOINER,
Metadata, Metadata,
@@ -60,12 +65,9 @@ from picard.script import (
ScriptSyntaxError, ScriptSyntaxError,
ScriptUnicodeError, ScriptUnicodeError,
ScriptUnknownFunction, ScriptUnknownFunction,
register_script_function,
script_function,
script_function_documentation, script_function_documentation,
script_function_documentation_all, script_function_documentation_all,
) )
from picard.script.functions import FunctionRegistryItem
try: try:
@@ -170,77 +172,77 @@ class ScriptParserTest(PicardTestCase):
with self.assertRaisesRegex(ScriptUnicodeError, areg): with self.assertRaisesRegex(ScriptUnicodeError, areg):
self.parser.eval("\\ufffg") 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): def test_script_function_decorator_default(self):
# test default decorator and default prefix # test default decorator and default prefix
with patch.object(ScriptParser, '_function_registry', ExtensionPoint()): @script_function()
@script_function() def func_somefunc(parser):
def func_somefunc(parser): return "x"
return "x" self.assertScriptResultEquals("$somefunc()", "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): def test_script_function_decorator_no_prefix(self):
# function without prefix # function without prefix
with patch.object(ScriptParser, '_function_registry', ExtensionPoint()): @script_function()
@script_function() def somefunc(parser):
def somefunc(parser): return "x"
return "x" self.assertScriptResultEquals("$somefunc()", "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): def test_script_function_decorator_arg(self):
# function with argument # function with argument
with patch.object(ScriptParser, '_function_registry', ExtensionPoint()): @script_function()
@script_function() def somefunc(parser, arg):
def somefunc(parser, arg): return arg
return arg
@script_function() @script_function()
def title(parser, arg): def title(parser, arg):
return arg.upper() return arg.upper()
self.assertScriptResultEquals("$somefunc($title(x))", "X") self.assertScriptResultEquals("$somefunc($title(x))", "X")
areg = r"^\d+:\d+:\$somefunc: Wrong number of arguments for \$somefunc: Expected exactly 1" areg = r"^\d+:\d+:\$somefunc: Wrong number of arguments for \$somefunc: Expected exactly 1"
with self.assertRaisesRegex(ScriptError, areg): with self.assertRaisesRegex(ScriptError, areg):
self.parser.eval("$somefunc()") 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): def test_script_function_decorator_argcount(self):
# ignore argument count # ignore argument count
with patch.object(ScriptParser, '_function_registry', ExtensionPoint()): @script_function(check_argcount=False)
@script_function(check_argcount=False) def somefunc(parser, *arg):
def somefunc(parser, *arg): return str(len(arg))
return str(len(arg)) self.assertScriptResultEquals("$somefunc(a,b,c)", "3")
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): def test_script_function_decorator_altname(self):
# alternative name # alternative name
with patch.object(ScriptParser, '_function_registry', ExtensionPoint()): @script_function(name="otherfunc")
@script_function(name="otherfunc") def somefunc4(parser):
def somefunc4(parser): return "x"
return "x" self.assertScriptResultEquals("$otherfunc()", "x")
self.assertScriptResultEquals("$otherfunc()", "x") areg = r"^\d+:\d+:\$somefunc: Unknown function '\$somefunc'"
areg = r"^\d+:\d+:\$somefunc: Unknown function '\$somefunc'" with self.assertRaisesRegex(ScriptError, areg):
with self.assertRaisesRegex(ScriptError, areg): self.parser.eval("$somefunc()")
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): def test_script_function_decorator_altprefix(self):
# alternative prefix # alternative prefix
with patch.object(ScriptParser, '_function_registry', ExtensionPoint()): @script_function(prefix='theprefix_')
@script_function(prefix='theprefix_') def theprefix_somefunc(parser):
def theprefix_somefunc(parser): return "x"
return "x" self.assertScriptResultEquals("$somefunc()", "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): def test_script_function_decorator_eval_args(self):
# disable argument evaluation # disable argument evaluation
with patch.object(ScriptParser, '_function_registry', ExtensionPoint()): @script_function(eval_args=False)
@script_function(eval_args=False) def somefunc(parser, arg):
def somefunc(parser, arg): return arg.eval(parser)
return arg.eval(parser)
@script_function() @script_function()
def title(parser, arg): def title(parser, arg):
return arg.upper() return arg.upper()
self.assertScriptResultEquals("$somefunc($title(x))", "X") self.assertScriptResultEquals("$somefunc($title(x))", "X")
@staticmethod @staticmethod
def assertStartswith(text, expect): def assertStartswith(text, expect):
@@ -252,98 +254,95 @@ class ScriptParserTest(PicardTestCase):
if not text.endswith(expect): if not text.endswith(expect):
raise AssertionError("do not end with %r but with %r" % (expect, text[-len(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): def test_script_function_documentation_nodoc(self):
"""test script_function_documentation() with a function without documentation""" """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() doc = script_function_documentation('nodocfunc', 'markdown')
def func_nodocfunc(parser): self.assertEqual(doc, '')
return "" 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): def test_script_function_documentation(self):
"""test script_function_documentation() with a function with documentation""" """test script_function_documentation() with a function with documentation"""
with patch.object(ScriptParser, '_function_registry', ExtensionPoint()): # the documentation used to test includes backquotes
# the documentation used to test includes backquotes testdoc = '`$somefunc()`'
testdoc = '`$somefunc()`'
@script_function(documentation=testdoc) @script_function(documentation=testdoc)
def func_somefunc(parser): def func_somefunc(parser):
return "x" return "x"
doc = script_function_documentation('somefunc', 'markdown') doc = script_function_documentation('somefunc', 'markdown')
self.assertEqual(doc, testdoc) self.assertEqual(doc, testdoc)
areg = r"^no such documentation format: unknownformat" areg = r"^no such documentation format: unknownformat"
with self.assertRaisesRegex(ScriptFunctionDocError, areg): with self.assertRaisesRegex(ScriptFunctionDocError, areg):
script_function_documentation('somefunc', 'unknownformat') script_function_documentation('somefunc', 'unknownformat')
@unittest.skipUnless(markdown, "markdown module missing") @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): def test_script_function_documentation_html(self):
"""test script_function_documentation() with a function with documentation""" """test script_function_documentation() with a function with documentation"""
with patch.object(ScriptParser, '_function_registry', ExtensionPoint()): # get html code as generated by markdown
# get html code as generated by markdown pre, post = markdown('`XXX`').split('XXX')
pre, post = markdown('`XXX`').split('XXX')
# the documentation used to test includes backquotes # the documentation used to test includes backquotes
testdoc = '`$somefunc()`' testdoc = '`$somefunc()`'
@script_function(documentation=testdoc) @script_function(documentation=testdoc)
def func_somefunc(parser): def func_somefunc(parser):
return "x" return "x"
doc = script_function_documentation('somefunc', 'html') doc = script_function_documentation('somefunc', 'html')
self.assertEqual(doc, pre + '$somefunc()' + post) 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): def test_script_function_documentation_unknown_function(self):
"""test script_function_documentation() with an unknown function""" """test script_function_documentation() with an unknown function"""
with patch.object(ScriptParser, '_function_registry', ExtensionPoint()): areg = r"^no such function: unknownfunc"
areg = r"^no such function: unknownfunc" with self.assertRaisesRegex(ScriptFunctionDocError, areg):
with self.assertRaisesRegex(ScriptFunctionDocError, areg): script_function_documentation('unknownfunc', 'html')
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): def test_script_function_documentation_all(self):
"""test script_function_documentation_all() with markdown format""" """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') @script_function(documentation='somedoc1')
def func_somefunc2(parser): def func_somefunc1(parser):
return "x" return "x"
@script_function(documentation='somedoc1') docall = script_function_documentation_all()
def func_somefunc1(parser): self.assertEqual(docall, 'somedoc1\nsomedoc2')
return "x"
docall = script_function_documentation_all()
self.assertEqual(docall, 'somedoc1\nsomedoc2')
@unittest.skipUnless(markdown, "markdown module missing") @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): def test_script_function_documentation_all_html(self):
"""test script_function_documentation_all() with html format""" """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 @script_function(documentation='somedoc')
pre, post = markdown('XXX').split('XXX') def func_somefunc(parser):
return "x"
@script_function(documentation='somedoc') def postprocessor(data, function):
def func_somefunc(parser): return 'w' + data + function.name + 'y'
return "x"
def postprocessor(data, function): docall = script_function_documentation_all(
return 'w' + data + function.name + 'y' fmt='html',
pre='<div id="test">',
post="</div>\n",
postprocessor=postprocessor,
)
docall = script_function_documentation_all( self.assertStartswith(docall, '<div id="test">w' + pre)
fmt='html', self.assertEndswith(docall, post + 'somefuncy</div>\n')
pre='<div id="test">',
post="</div>\n",
postprocessor=postprocessor,
)
self.assertStartswith(docall, '<div id="test">w' + pre)
self.assertEndswith(docall, post + 'somefuncy</div>\n')
def test_unknown_function(self): def test_unknown_function(self):
areg = r"^\d+:\d+:\$unknownfunction: Unknown function '\$unknownfunction'" areg = r"^\d+:\d+:\$unknownfunction: Unknown function '\$unknownfunction'"

View File

@@ -26,9 +26,9 @@ from test.picardtestcase import PicardTestCase
from picard import config from picard import config
from picard.const.sys import IS_WIN from picard.const.sys import IS_WIN
from picard.extension_points.script_functions import register_script_function
from picard.file import File from picard.file import File
from picard.metadata import Metadata from picard.metadata import Metadata
from picard.script import register_script_function
from picard.util.scripttofilename import ( from picard.util.scripttofilename import (
script_to_filename, script_to_filename,
script_to_filename_with_metadata, script_to_filename_with_metadata,