diff --git a/picard/album.py b/picard/album.py
index 95a9c3f38..cbaf7febb 100644
--- a/picard/album.py
+++ b/picard/album.py
@@ -82,7 +82,7 @@ from picard.plugin import (
from picard.script import (
ScriptError,
ScriptParser,
- enabled_tagger_scripts_texts,
+ iter_active_tagging_scripts,
)
from picard.track import Track
from picard.util import (
@@ -474,21 +474,21 @@ class Album(DataObject, MetadataItem):
track.metadata_images_changed.connect(self.update_metadata_images)
# Prepare parser for user's script
- for s_name, s_text in enabled_tagger_scripts_texts():
+ for script in iter_active_tagging_scripts():
parser = ScriptParser()
for track in self._new_tracks:
# Run tagger script for each track
try:
- parser.eval(s_text, track.metadata)
+ parser.eval(script.content, track.metadata)
except ScriptError:
- log.exception("Failed to run tagger script %s on track", s_name)
+ log.exception("Failed to run tagger script %s on track", script.name)
track.metadata.strip_whitespace()
track.scripted_metadata.update(track.metadata)
# Run tagger script for the album itself
try:
- parser.eval(s_text, self._new_metadata)
+ parser.eval(script.content, self._new_metadata)
except ScriptError:
- log.exception("Failed to run tagger script %s on album", s_name)
+ log.exception("Failed to run tagger script %s on album", script.name)
self._new_metadata.strip_whitespace()
unmatched_files = [file for track in self.tracks for file in track.files]
diff --git a/picard/config_upgrade.py b/picard/config_upgrade.py
index 0b34f0743..ee0b7685c 100644
--- a/picard/config_upgrade.py
+++ b/picard/config_upgrade.py
@@ -429,19 +429,19 @@ def upgrade_to_v2_7_0dev3(config):
"""
from picard.script import get_file_naming_script_presets
from picard.script.serializer import (
- FileNamingScript,
- ScriptImportError,
+ FileNamingScriptInfo,
+ ScriptSerializerFromFileError,
)
scripts = {}
for item in config.setting.raw_value('file_naming_scripts') or []:
try:
- script_item = FileNamingScript().create_from_yaml(item, create_new_id=False)
+ script_item = FileNamingScriptInfo().create_from_yaml(item, create_new_id=False)
scripts[script_item['id']] = script_item.to_dict()
- except ScriptImportError:
+ except ScriptSerializerFromFileError:
log.error("Error converting file naming script")
script_list = set(scripts.keys()) | set(map(lambda item: item['id'], get_file_naming_script_presets()))
if config.setting['selected_file_naming_script_id'] not in script_list:
- script_item = FileNamingScript(
+ script_item = FileNamingScriptInfo(
script=config.setting.value('file_naming_format', TextOption),
title=_("Primary file naming script"),
readonly=False,
diff --git a/picard/script/__init__.py b/picard/script/__init__.py
index 8d9ec9ccf..594eb6445 100644
--- a/picard/script/__init__.py
+++ b/picard/script/__init__.py
@@ -63,7 +63,43 @@ from picard.script.parser import ( # noqa: F401 # pylint: disable=unused-import
ScriptUnknownFunction,
ScriptVariable,
)
-from picard.script.serializer import FileNamingScript
+from picard.script.serializer import FileNamingScriptInfo
+
+
+class TaggingScriptSetting:
+ def __init__(self, pos=0, name="", enabled=False, content=""):
+ self.pos = pos
+ self.name = name
+ self.enabled = enabled
+ self.content = content
+
+
+def iter_tagging_scripts_from_config(config=None):
+ if config is None:
+ config = get_config()
+ yield from iter_tagging_scripts_from_tuples(config.setting['list_of_scripts'])
+
+
+def iter_tagging_scripts_from_tuples(tuples):
+ for pos, name, enabled, content in tuples:
+ yield TaggingScriptSetting(pos=pos, name=name, enabled=enabled, content=content)
+
+
+def save_tagging_scripts_to_config(scripts, config=None):
+ if config is None:
+ config = get_config()
+ config.setting['list_of_scripts'] = [(s.pos, s.name, s.enabled, s.content) for s in scripts]
+
+
+def iter_active_tagging_scripts(config=None):
+ """Returns an iterator over the enabled and not empty tagging scripts."""
+ if config is None:
+ config = get_config()
+ if not config.setting['enable_tagger_scripts']:
+ return
+ for script in iter_tagging_scripts_from_config(config=config):
+ if script.enabled and script.content:
+ yield script
class ScriptFunctionDocError(Exception):
@@ -111,15 +147,6 @@ def script_function_documentation_all(fmt='markdown', pre='',
return "\n".join(doc_elements)
-def enabled_tagger_scripts_texts():
- """Returns an iterator over the enabled tagger scripts.
- For each script, you'll get a tuple consisting of the script name and text"""
- config = get_config()
- if not config.setting['enable_tagger_scripts']:
- return []
- return [(s_name, s_text) for _s_pos, s_name, s_enabled, s_text in config.setting['list_of_scripts'] if s_enabled and s_text]
-
-
def get_file_naming_script(settings):
"""Retrieve the file naming script.
@@ -146,7 +173,7 @@ def get_file_naming_script_presets():
"""Generator of preset example file naming script objects.
Yields:
- FileNamingScript: the next example FileNamingScript object
+ FileNamingScriptInfo: the next example FileNamingScriptInfo object
"""
AUTHOR = "MusicBrainz Picard Development Team"
DESCRIPTION = _("This preset example file naming script does not require any special settings, tagging scripts or plugins.")
@@ -158,7 +185,7 @@ def get_file_naming_script_presets():
'title': _(title),
}
- yield FileNamingScript(
+ yield FileNamingScriptInfo(
id=DEFAULT_NAMING_PRESET_ID,
title=preset_title(1, N_("Default file naming script")),
script=DEFAULT_FILE_NAMING_FORMAT,
@@ -170,7 +197,7 @@ def get_file_naming_script_presets():
script_language_version="1.0",
)
- yield FileNamingScript(
+ yield FileNamingScriptInfo(
id="Preset 2",
title=preset_title(2, N_("[album artist]/[album]/[track #]. [title]")),
script="%albumartist%/\n"
@@ -184,7 +211,7 @@ def get_file_naming_script_presets():
script_language_version="1.0",
)
- yield FileNamingScript(
+ yield FileNamingScriptInfo(
id="Preset 3",
title=preset_title(3, N_("[album artist]/[album]/[disc and track #] [artist] - [title]")),
script="$if2(%albumartist%,%artist%)/\n"
diff --git a/picard/script/serializer.py b/picard/script/serializer.py
index 65763bfb9..cc5a53dd0 100644
--- a/picard/script/serializer.py
+++ b/picard/script/serializer.py
@@ -49,7 +49,7 @@ from picard.util import make_filename_from_title
@unique
-class PicardScriptType(IntEnum):
+class ScriptSerializerType(IntEnum):
"""Picard Script object types
"""
BASE = 0
@@ -57,16 +57,28 @@ class PicardScriptType(IntEnum):
FILENAMING = 2
-class ScriptImportExportError(Exception):
+class ScriptSerializerError(Exception):
+ """Base exception class for ScriptSerializer errors"""
+
+
+class ScriptSerializerImportExportError(ScriptSerializerError):
def __init__(self, *args, format=None, filename=None, error_msg=None):
+ super().__init__(*args)
self.format = format
self.filename = filename
self.error_msg = error_msg
-class ScriptImportError(Exception):
- def __init__(self, *args):
- super().__init__(*args)
+class ScriptSerializerImportError(ScriptSerializerImportExportError):
+ """Exception raised during script import"""
+
+
+class ScriptSerializerExportError(ScriptSerializerImportExportError):
+ """Exception raised during script export"""
+
+
+class ScriptSerializerFromFileError(ScriptSerializerError):
+ """Exception raised when converting a file to a ScriptSerializer"""
class MultilineLiteral(str):
@@ -80,12 +92,12 @@ class MultilineLiteral(str):
yaml.add_representer(MultilineLiteral, MultilineLiteral.yaml_presenter)
-class PicardScript():
+class ScriptSerializer():
"""Base class for Picard script objects.
"""
# Base class developed to support future tagging script class as possible replacement for currently used tuples in config.setting["list_of_scripts"].
- TYPE = PicardScriptType.BASE
+ TYPE = ScriptSerializerType.BASE
OUTPUT_FIELDS = ('title', 'script_language_version', 'script', 'id')
# Don't automatically trigger changing the `script_last_updated` property when updating these properties.
@@ -226,7 +238,7 @@ class PicardScript():
with open(filename, 'w', encoding='utf-8') as o_file:
o_file.write(script_text)
except OSError as error:
- raise ScriptImportExportError(format=FILE_ERROR_EXPORT, filename=filename, error_msg=error.strerror)
+ raise ScriptSerializerExportError(format=FILE_ERROR_EXPORT, filename=filename, error_msg=error.strerror)
dialog = QtWidgets.QMessageBox(
QtWidgets.QMessageBox.Icon.Information,
_("Export Script"),
@@ -255,14 +267,14 @@ class PicardScript():
with open(filename, 'r', encoding='utf-8') as i_file:
file_content = i_file.read()
except OSError as error:
- raise ScriptImportExportError(format=FILE_ERROR_IMPORT, filename=filename, error_msg=error.strerror)
+ raise ScriptSerializerImportError(format=FILE_ERROR_IMPORT, filename=filename, error_msg=error.strerror)
if not file_content.strip():
- raise ScriptImportExportError(format=FILE_ERROR_IMPORT, filename=filename, error_msg=N_("The file was empty"))
+ raise ScriptSerializerImportError(format=FILE_ERROR_IMPORT, filename=filename, error_msg=N_("The file was empty"))
if file_type == cls._file_types()['package']:
try:
return cls().create_from_yaml(file_content)
- except ScriptImportError as error:
- raise ScriptImportExportError(format=FILE_ERROR_DECODE, filename=filename, error_msg=error)
+ except ScriptSerializerFromFileError as error:
+ raise ScriptSerializerImportError(format=FILE_ERROR_DECODE, filename=filename, error_msg=error)
else:
return cls(
title=_("Imported from %s") % filename,
@@ -283,9 +295,9 @@ class PicardScript():
"""
new_object = cls()
if not isinstance(script_dict, Mapping):
- raise ScriptImportError(N_("Argument is not a dictionary"))
+ raise ScriptSerializerFromFileError(N_("Argument is not a dictionary"))
if 'title' not in script_dict or 'script' not in script_dict:
- raise ScriptImportError(N_("Invalid script package"))
+ raise ScriptSerializerFromFileError(N_("Invalid script package"))
new_object.update_from_dict(script_dict)
if create_new_id or not new_object['id']:
new_object._set_new_id()
@@ -327,9 +339,9 @@ class PicardScript():
new_object = cls()
yaml_dict = yaml.safe_load(yaml_string)
if not isinstance(yaml_dict, dict):
- raise ScriptImportError(N_("File content not a dictionary"))
+ raise ScriptSerializerFromFileError(N_("File content not a dictionary"))
if 'title' not in yaml_dict or 'script' not in yaml_dict:
- raise ScriptImportError(N_("Invalid script package"))
+ raise ScriptSerializerFromFileError(N_("Invalid script package"))
new_object.update_from_dict(yaml_dict)
if create_new_id or not new_object['id']:
new_object._set_new_id()
@@ -367,10 +379,10 @@ class PicardScript():
))
-class TaggingScript(PicardScript):
+class TaggingScriptInfo(ScriptSerializer):
"""Picard tagging script class
"""
- TYPE = PicardScriptType.TAGGER
+ TYPE = ScriptSerializerType.TAGGER
OUTPUT_FIELDS = ('title', 'script_language_version', 'script', 'id')
def __init__(self, script='', title='', id=None, last_updated=None, script_language_version=None):
@@ -385,10 +397,10 @@ class TaggingScript(PicardScript):
super().__init__(script=script, title=title, id=id, last_updated=last_updated, script_language_version=script_language_version)
-class FileNamingScript(PicardScript):
+class FileNamingScriptInfo(ScriptSerializer):
"""Picard file naming script class
"""
- TYPE = PicardScriptType.FILENAMING
+ TYPE = ScriptSerializerType.FILENAMING
OUTPUT_FIELDS = ('title', 'description', 'author', 'license', 'version', 'last_updated', 'script_language_version', 'script', 'id')
def __init__(
diff --git a/picard/track.py b/picard/track.py
index d3c35cd77..7d42fd3ff 100644
--- a/picard/track.py
+++ b/picard/track.py
@@ -71,7 +71,7 @@ from picard.metadata import (
from picard.script import (
ScriptError,
ScriptParser,
- enabled_tagger_scripts_texts,
+ iter_active_tagging_scripts,
)
from picard.util import pattern_as_regex
from picard.util.imagelist import ImageList
@@ -201,12 +201,12 @@ class Track(DataObject, FileListItem):
@staticmethod
def run_scripts(metadata, strip_whitespace=False):
- for s_name, s_text in enabled_tagger_scripts_texts():
+ for script in iter_active_tagging_scripts():
parser = ScriptParser()
try:
- parser.eval(s_text, metadata)
+ parser.eval(script.content, metadata)
except ScriptError:
- log.exception("Failed to run tagger script %s on track", s_name)
+ log.exception("Failed to run tagger script %s on track", script.name)
if strip_whitespace:
metadata.strip_whitespace()
diff --git a/picard/ui/itemviews/basetreeview.py b/picard/ui/itemviews/basetreeview.py
index d223d5cbc..82003dab5 100644
--- a/picard/ui/itemviews/basetreeview.py
+++ b/picard/ui/itemviews/basetreeview.py
@@ -75,6 +75,7 @@ from picard.extension_points.item_actions import (
)
from picard.file import File
from picard.i18n import gettext as _
+from picard.script import iter_tagging_scripts_from_tuples
from picard.track import (
NonAlbumTrack,
Track,
@@ -403,8 +404,6 @@ class BaseTreeView(QtWidgets.QTreeWidget):
CollectionMenu(selected_albums, _("Collections"), menu),
)
- scripts = config.setting['list_of_scripts']
-
if plugin_actions:
plugin_menu = QtWidgets.QMenu(_("P&lugins"), menu)
plugin_menu.setIcon(self.icon_plugins)
@@ -424,8 +423,9 @@ class BaseTreeView(QtWidgets.QTreeWidget):
action_menu = plugin_menus[key] = action_menu.addMenu(key[-1])
action_menu.addAction(action)
+ scripts = config.setting['list_of_scripts']
if scripts:
- scripts_menu = ScriptsMenu(scripts, _("&Run scripts"), menu)
+ scripts_menu = ScriptsMenu(iter_tagging_scripts_from_tuples(scripts), _("&Run scripts"), menu)
scripts_menu.setIcon(self.icon_plugins)
add_actions(
'-',
diff --git a/picard/ui/options/profiles.py b/picard/ui/options/profiles.py
index 221349e46..07dba374f 100644
--- a/picard/ui/options/profiles.py
+++ b/picard/ui/options/profiles.py
@@ -44,7 +44,10 @@ from picard.i18n import (
gettext_constants,
)
from picard.profile import profile_groups_values
-from picard.script import get_file_naming_script_presets
+from picard.script import (
+ get_file_naming_script_presets,
+ iter_tagging_scripts_from_tuples,
+)
from picard.util import get_base_title
from picard.ui.forms.ui_options_profiles import Ui_ProfileEditorDialog
@@ -249,7 +252,7 @@ class ProfilesOptionsPage(OptionsPage):
return _("Unknown script")
def _get_scripts_list(self, scripts):
- enabled_scripts = ['
%s' % name for (pos, name, enabled, script) in scripts if enabled]
+ enabled_scripts = ['%s' % s.name for s in iter_tagging_scripts_from_tuples(scripts) if s.enabled]
if not enabled_scripts:
return _("No enabled scripts")
return _("Enabled scripts:") + '' + "".join(enabled_scripts) + '
'
diff --git a/picard/ui/options/scripting.py b/picard/ui/options/scripting.py
index b75158112..d3508c0b1 100644
--- a/picard/ui/options/scripting.py
+++ b/picard/ui/options/scripting.py
@@ -40,10 +40,15 @@ from picard.i18n import (
N_,
gettext as _,
)
-from picard.script import ScriptParser
+from picard.script import (
+ ScriptParser,
+ TaggingScriptSetting,
+ iter_tagging_scripts_from_config,
+ save_tagging_scripts_to_config,
+)
from picard.script.serializer import (
- ScriptImportExportError,
- TaggingScript,
+ ScriptSerializerImportExportError,
+ TaggingScriptInfo,
)
from picard.ui import (
@@ -146,12 +151,12 @@ class ScriptingOptionsPage(OptionsPage):
error_message = _(fmt) % params
self.display_error(ScriptFileError(_(title), error_message))
- def output_file_error(self, error: ScriptImportExportError):
+ def output_file_error(self, error: ScriptSerializerImportExportError):
"""Log file error and display error message dialog.
Args:
fmt (str): Format for the error type being displayed
- error (ScriptImportExportError): The error as a ScriptImportExportError instance
+ error (ScriptSerializerImportExportError): The error as a ScriptSerializerImportExportError instance
"""
params = {
'filename': error.filename,
@@ -164,13 +169,14 @@ class ScriptingOptionsPage(OptionsPage):
a Picard script package.
"""
try:
- script_item = TaggingScript().import_script(self)
- except ScriptImportExportError as error:
+ tagging_script = TaggingScriptInfo().import_script(self)
+ except ScriptSerializerImportExportError as error:
self.output_file_error(error)
return
- if script_item:
- title = _("%s (imported)") % script_item['title']
- list_item = ScriptListWidgetItem(title, False, script_item['script'])
+ if tagging_script:
+ title = _("%s (imported)") % tagging_script['title']
+ script = TaggingScriptSetting(name=title, enabled=False, content=tagging_script['script'])
+ list_item = ScriptListWidgetItem(script)
self.ui.script_list.addItem(list_item)
self.ui.script_list.setCurrentRow(self.ui.script_list.count() - 1)
@@ -178,19 +184,19 @@ class ScriptingOptionsPage(OptionsPage):
"""Export the current script to an external file. Export can be either as a plain text
script or a naming script package.
"""
- items = self.ui.script_list.selectedItems()
- if not items:
+ list_items = self.ui.script_list.selectedItems()
+ if not list_items:
return
- item = items[0]
- script_text = item.script
- script_title = item.name if item.name.strip() else _("Unnamed Script")
-
- if script_text:
- script_item = TaggingScript(title=script_title, script=script_text)
+ list_item = list_items[0]
+ content = list_item.script.content
+ if content:
+ name = list_item.script.name.strip()
+ title = name or _("Unnamed Script")
+ tagging_script = TaggingScriptInfo(title=title, script=content)
try:
- script_item.export_script(parent=self)
- except ScriptImportExportError as error:
+ tagging_script.export_script(parent=self)
+ except ScriptSerializerImportExportError as error:
self.output_file_error(error)
def enable_tagger_scripts_toggled(self, on):
@@ -198,11 +204,11 @@ class ScriptingOptionsPage(OptionsPage):
self.ui.script_list.add_script()
def script_selected(self):
- items = self.ui.script_list.selectedItems()
- if items:
- item = items[0]
+ list_items = self.ui.script_list.selectedItems()
+ if list_items:
+ list_item = list_items[0]
self.ui.tagger_script.setEnabled(True)
- self.ui.tagger_script.setText(item.script)
+ self.ui.tagger_script.setText(list_item.script.content)
self.ui.tagger_script.setFocus(QtCore.Qt.FocusReason.OtherFocusReason)
self.ui.export_button.setEnabled(True)
else:
@@ -211,21 +217,21 @@ class ScriptingOptionsPage(OptionsPage):
self.ui.export_button.setEnabled(False)
def live_update_and_check(self):
- items = self.ui.script_list.selectedItems()
- if not items:
+ list_items = self.ui.script_list.selectedItems()
+ if not list_items:
return
- script = items[0]
- script.script = self.ui.tagger_script.toPlainText()
+ list_item = list_items[0]
+ list_item.script.content = self.ui.tagger_script.toPlainText()
self.ui.script_error.setStyleSheet("")
self.ui.script_error.setText("")
try:
self.check()
except OptionsCheckError as e:
- script.has_error = True
+ list_item.has_error = True
self.ui.script_error.setStyleSheet(self.STYLESHEET_ERROR)
self.ui.script_error.setText(e.info)
return
- script.has_error = False
+ list_item.has_error = False
def reset_selected_item(self):
widget = self.ui.script_list
@@ -248,8 +254,8 @@ class ScriptingOptionsPage(OptionsPage):
config = get_config()
self.ui.enable_tagger_scripts.setChecked(config.setting['enable_tagger_scripts'])
self.ui.script_list.clear()
- for pos, name, enabled, text in config.setting['list_of_scripts']:
- list_item = ScriptListWidgetItem(name, enabled, text)
+ for script in iter_tagging_scripts_from_config(config=config):
+ list_item = ScriptListWidgetItem(script)
self.ui.script_list.addItem(list_item)
# Select the last selected script item
@@ -261,12 +267,12 @@ class ScriptingOptionsPage(OptionsPage):
def _all_scripts(self):
for item in qlistwidget_items(self.ui.script_list):
- yield item.get_all()
+ yield item.get_script()
def save(self):
config = get_config()
config.setting['enable_tagger_scripts'] = self.ui.enable_tagger_scripts.isChecked()
- config.setting['list_of_scripts'] = list(self._all_scripts())
+ save_tagging_scripts_to_config(self._all_scripts())
config.persist['last_selected_script_pos'] = self.ui.script_list.currentRow()
def display_error(self, error):
diff --git a/picard/ui/scripteditor.py b/picard/ui/scripteditor.py
index 867ccc4bd..ade1697de 100644
--- a/picard/ui/scripteditor.py
+++ b/picard/ui/scripteditor.py
@@ -57,10 +57,11 @@ from picard.script import (
ScriptParser,
get_file_naming_script,
get_file_naming_script_presets,
+ iter_tagging_scripts_from_tuples,
)
from picard.script.serializer import (
- FileNamingScript,
- ScriptImportExportError,
+ FileNamingScriptInfo,
+ ScriptSerializerImportExportError,
)
from picard.util import (
get_base_title,
@@ -162,10 +163,10 @@ class ScriptEditorExamples():
try:
# Only apply scripts if the original file metadata has not been changed.
if self.settings['enable_tagger_scripts'] and not c_metadata.diff(file.orig_metadata):
- for s_pos, s_name, s_enabled, s_text in self.settings['list_of_scripts']:
- if s_enabled and s_text:
+ for s in iter_tagging_scripts_from_tuples(self.settings['list_of_scripts']):
+ if s.enabled and s.content:
parser = ScriptParser()
- parser.eval(s_text, c_metadata)
+ parser.eval(s.content, c_metadata)
filename_before = file.filename
filename_after = file.make_filename(filename_before, c_metadata, self.settings, self.script_text)
if not self.settings['move_files']:
@@ -385,7 +386,7 @@ def populate_script_selection_combo_box(naming_scripts, selected_script_id, comb
"""Populate the specified script selection combo box and identify the selected script.
Args:
- naming_scripts (dict): Dictionary of available user-defined naming scripts as script dictionaries as produced by FileNamingScript().to_dict()
+ naming_scripts (dict): Dictionary of available user-defined naming scripts as script dictionaries as produced by FileNamingScriptInfo().to_dict()
selected_script_id (str): ID code for the currently selected script
combo_box (QComboBox): Combo box object to populate
@@ -935,7 +936,7 @@ class ScriptEditorDialog(PicardDialog, SingletonDialog):
"""Insert a new item into the script selection combo box and update the script list in the settings.
Args:
- script_item (dict): File naming script to insert as produced by FileNamingScript().to_dict()
+ script_item (dict): File naming script to insert as produced by FileNamingScriptInfo().to_dict()
"""
self.selected_script_id = script_item['id']
self.naming_scripts[self.selected_script_id] = script_item
@@ -959,7 +960,7 @@ class ScriptEditorDialog(PicardDialog, SingletonDialog):
def new_script(self, script):
"""Add a new script to the script selection combo box and script list.
"""
- script_item = FileNamingScript(script=script)
+ script_item = FileNamingScriptInfo(script=script)
script_item.title = self.new_script_name()
self._insert_item(script_item.to_dict())
@@ -967,7 +968,7 @@ class ScriptEditorDialog(PicardDialog, SingletonDialog):
"""Add a copy of the script as a new editable script to the script selection combo box.
"""
selected, script_item = self.get_selected_index_and_item()
- new_item = FileNamingScript.create_from_dict(script_dict=script_item).copy()
+ new_item = FileNamingScriptInfo.create_from_dict(script_dict=script_item).copy()
base_title = "%s %s" % (get_base_title(script_item['title']), gettext_constants(DEFAULT_COPY_TEXT))
new_item.title = self.new_script_name(base_title)
@@ -1008,7 +1009,7 @@ class ScriptEditorDialog(PicardDialog, SingletonDialog):
"""Get the specified item from the script selection combo box.
Returns:
- dict: File naming script dictionary as produced by FileNamingScript().to_dict()
+ dict: File naming script dictionary as produced by FileNamingScriptInfo().to_dict()
"""
return self.get_script_item(self.ui.preset_naming_scripts.currentIndex())
@@ -1057,7 +1058,7 @@ class ScriptEditorDialog(PicardDialog, SingletonDialog):
Args:
idx (int): Index of the item to update
- script_item (dict): Updated file naming script information as produced by FileNamingScript().to_dict()
+ script_item (dict): Updated file naming script information as produced by FileNamingScriptInfo().to_dict()
"""
self.ui.preset_naming_scripts.setItemData(idx, script_item)
title = script_item['title']
@@ -1232,8 +1233,8 @@ class ScriptEditorDialog(PicardDialog, SingletonDialog):
a naming script package.
"""
try:
- script_item = FileNamingScript().import_script(self)
- except ScriptImportExportError as error:
+ script_item = FileNamingScriptInfo().import_script(self)
+ except ScriptSerializerImportExportError as error:
self.output_file_error(error.format, error.filename, error.error_msg)
return
if script_item:
@@ -1276,11 +1277,11 @@ class ScriptEditorDialog(PicardDialog, SingletonDialog):
script or a naming script package.
"""
selected = self.get_selected_item()
- script_item = FileNamingScript.create_from_dict(script_dict=selected, create_new_id=False)
+ script_item = FileNamingScriptInfo.create_from_dict(script_dict=selected, create_new_id=False)
script_item.title = get_base_title(script_item.title)
try:
script_item.export_script(parent=self)
- except ScriptImportExportError as error:
+ except ScriptSerializerImportExportError as error:
self.output_file_error(error.format, error.filename, error.error_msg)
def check_formats(self):
@@ -1401,7 +1402,7 @@ class ScriptDetailsEditor(PicardDialog):
def set_last_updated(self):
"""Set the last updated value to the current timestamp.
"""
- self.ui.script_last_updated.setText(FileNamingScript.make_last_updated())
+ self.ui.script_last_updated.setText(FileNamingScriptInfo.make_last_updated())
self.ui.script_last_updated.setModified(True)
def save_changes(self):
diff --git a/picard/ui/scriptsmenu.py b/picard/ui/scriptsmenu.py
index 00f55b08b..11b991e39 100644
--- a/picard/ui/scriptsmenu.py
+++ b/picard/ui/scriptsmenu.py
@@ -46,23 +46,21 @@ class ScriptsMenu(QtWidgets.QMenu):
super().__init__(*args)
for script in scripts:
- action = self.addAction(script[1])
+ action = self.addAction(script.name)
action.triggered.connect(partial(self._run_script, script))
def _run_script(self, script):
- s_name = script[1]
- s_text = script[3]
parser = ScriptParser()
for obj in self._iter_unique_metadata_objects():
try:
- parser.eval(s_text, obj.metadata)
+ parser.eval(script.content, obj.metadata)
obj.update()
except ScriptError as e:
- log.exception('Error running tagger script "%s" on object %r', s_name, obj)
+ log.exception('Error running tagger script "%s" on object %r', script.name, obj)
msg = N_('Script error in "%(script)s": %(message)s')
mparms = {
- 'script': s_name,
+ 'script': script.name,
'message': str(e),
}
self.tagger.window.set_statusbar_message(msg, mparms)
diff --git a/picard/ui/widgets/scriptlistwidget.py b/picard/ui/widgets/scriptlistwidget.py
index 1b7706308..a39f184cc 100644
--- a/picard/ui/widgets/scriptlistwidget.py
+++ b/picard/ui/widgets/scriptlistwidget.py
@@ -35,6 +35,7 @@ from picard.i18n import (
gettext as _,
gettext_constants,
)
+from picard.script import TaggingScriptSetting
from picard.util import unique_numbered_title
from picard.ui import HashableListWidgetItem
@@ -77,7 +78,7 @@ class ScriptListWidget(QtWidgets.QListWidget):
def add_script(self):
numbered_name = self.unique_script_name()
- list_item = ScriptListWidgetItem(name=numbered_name)
+ list_item = ScriptListWidgetItem(TaggingScriptSetting(name=numbered_name, enabled=True))
list_item.setCheckState(QtCore.Qt.CheckState.Checked)
self.addItem(list_item)
self.setCurrentItem(list_item, QtCore.QItemSelectionModel.SelectionFlag.Clear
@@ -114,14 +115,15 @@ class ScriptListWidget(QtWidgets.QListWidget):
class ScriptListWidgetItem(HashableListWidgetItem):
"""Holds a script's list and text widget properties"""
- def __init__(self, name=None, enabled=True, script=""):
- super().__init__(name)
+ def __init__(self, script):
+ assert isinstance(script, TaggingScriptSetting)
+ super().__init__(script.name)
self.setFlags(self.flags() | QtCore.Qt.ItemFlag.ItemIsUserCheckable | QtCore.Qt.ItemFlag.ItemIsEditable)
- if name is None:
- name = gettext_constants(DEFAULT_SCRIPT_NAME)
- self.setText(name)
- self.setCheckState(QtCore.Qt.CheckState.Checked if enabled else QtCore.Qt.CheckState.Unchecked)
- self.script = script
+ if not script.name:
+ script.name = gettext_constants(DEFAULT_SCRIPT_NAME)
+ self.setText(script.name)
+ self.setCheckState(QtCore.Qt.CheckState.Checked if script.enabled else QtCore.Qt.CheckState.Unchecked)
+ self._script = script
self.has_error = False
@property
@@ -136,6 +138,12 @@ class ScriptListWidgetItem(HashableListWidgetItem):
def enabled(self):
return self.checkState() == QtCore.Qt.CheckState.Checked
- def get_all(self):
- # tuples used to get pickle dump of settings to work
- return (self.pos, self.name, self.enabled, self.script)
+ @property
+ def script(self):
+ return self._script
+
+ def get_script(self):
+ self._script.pos = self.pos
+ self._script.name = self.name
+ self._script.enabled = self.enabled
+ return self._script
diff --git a/test/test_script_serializer.py b/test/test_script_serializer.py
index 943c06bf3..40f74f733 100644
--- a/test/test_script_serializer.py
+++ b/test/test_script_serializer.py
@@ -28,9 +28,9 @@ import yaml
from test.picardtestcase import PicardTestCase
from picard.script.serializer import (
- FileNamingScript,
- PicardScript,
- ScriptImportError,
+ FileNamingScriptInfo,
+ ScriptSerializer,
+ ScriptSerializerFromFileError,
)
@@ -43,14 +43,14 @@ class MockDateTime(datetime.datetime):
raise Exception("Unexpected parameter tz=%r" % tz)
-class PicardScriptTest(PicardTestCase):
+class ScriptSerializerTest(PicardTestCase):
def assertYamlEquals(self, yaml_str, obj, msg=None):
self.assertEqual(obj, yaml.safe_load(yaml_str), msg)
def test_script_object_1(self):
# Check initial loaded values.
- test_script = PicardScript(title='Script 1', script='Script text', id='12345', last_updated='2021-04-26', script_language_version='1.0')
+ test_script = ScriptSerializer(title='Script 1', script='Script text', id='12345', last_updated='2021-04-26', script_language_version='1.0')
self.assertEqual(test_script.id, '12345')
self.assertEqual(test_script['id'], '12345')
self.assertEqual(test_script.last_updated, '2021-04-26')
@@ -59,7 +59,7 @@ class PicardScriptTest(PicardTestCase):
def test_script_object_2(self):
# Check updating values directly so as not to modify `last_updated`.
- test_script = PicardScript(title='Script 1', script='Script text', id='12345', last_updated='2021-04-26')
+ test_script = ScriptSerializer(title='Script 1', script='Script text', id='12345', last_updated='2021-04-26')
test_script.id = '54321'
self.assertEqual(test_script.id, '54321')
self.assertEqual(test_script['id'], '54321')
@@ -73,7 +73,7 @@ class PicardScriptTest(PicardTestCase):
def test_script_object_3(self):
# Check updating values that are ignored from modifying `last_updated`.
- test_script = PicardScript(title='Script 1', script='Script text', id='12345', last_updated='2021-04-26')
+ test_script = ScriptSerializer(title='Script 1', script='Script text', id='12345', last_updated='2021-04-26')
test_script.update_script_setting(id='54321')
self.assertEqual(test_script.id, '54321')
self.assertEqual(test_script['id'], '54321')
@@ -83,7 +83,7 @@ class PicardScriptTest(PicardTestCase):
@patch('datetime.datetime', MockDateTime)
def test_script_object_4(self):
# Check updating values that modify `last_updated`.
- test_script = PicardScript(title='Script 1', script='Script text', id='12345', last_updated='2021-04-26')
+ test_script = ScriptSerializer(title='Script 1', script='Script text', id='12345', last_updated='2021-04-26')
test_script.update_script_setting(title='Updated Script 1')
self.assertEqual(test_script.title, 'Updated Script 1')
self.assertEqual(test_script['title'], 'Updated Script 1')
@@ -93,7 +93,7 @@ class PicardScriptTest(PicardTestCase):
@patch('datetime.datetime', MockDateTime)
def test_script_object_5(self):
# Check updating values from dict that modify `last_updated`.
- test_script = PicardScript(title='Script 1', script='Script text', id='12345', last_updated='2021-04-26')
+ test_script = ScriptSerializer(title='Script 1', script='Script text', id='12345', last_updated='2021-04-26')
test_script.update_from_dict({"script": "Updated script"})
self.assertEqual(test_script.script, 'Updated script')
self.assertEqual(test_script['script'], 'Updated script')
@@ -102,7 +102,7 @@ class PicardScriptTest(PicardTestCase):
def test_script_object_6(self):
# Test that extra (unknown) settings are ignored during updating
- test_script = PicardScript(title='Script 1', script='Script text', id='12345', last_updated='2021-04-26', script_language_version='1.0')
+ test_script = ScriptSerializer(title='Script 1', script='Script text', id='12345', last_updated='2021-04-26', script_language_version='1.0')
test_script.update_script_setting(description='Updated description')
self.assertEqual(test_script['last_updated'], '2021-04-26')
self.assertYamlEquals(test_script.to_yaml(), {"id": "12345", "script": "Script text\n", "script_language_version": "1.0", "title": "Script 1"})
@@ -111,7 +111,7 @@ class PicardScriptTest(PicardTestCase):
def test_script_object_7(self):
# Test that extra (unknown) settings are ignored during updating from dict
- test_script = PicardScript(title='Script 1', script='Script text', id='12345', last_updated='2021-04-26', script_language_version='1.0')
+ test_script = ScriptSerializer(title='Script 1', script='Script text', id='12345', last_updated='2021-04-26', script_language_version='1.0')
test_script.update_from_dict({"description": "Updated description"})
self.assertEqual(test_script['last_updated'], '2021-04-26')
self.assertYamlEquals(test_script.to_yaml(), {"id": "12345", "script": "Script text\n", "script_language_version": "1.0", "title": "Script 1"})
@@ -120,18 +120,18 @@ class PicardScriptTest(PicardTestCase):
def test_script_object_8(self):
# Test that requested unknown settings return None
- test_script = PicardScript(title='Script 1', script='Script text', id='12345', last_updated='2021-04-26')
+ test_script = ScriptSerializer(title='Script 1', script='Script text', id='12345', last_updated='2021-04-26')
self.assertEqual(test_script['unknown_setting'], None)
def test_script_object_9(self):
# Test that an exception is raised when creating or updating using an invalid YAML string
- with self.assertRaises(ScriptImportError):
- PicardScript().create_from_yaml('Not a YAML string')
- PicardScript(title='Script 1', script='Script text', id='12345', last_updated='2021-04-26', script_language_version='1.0')
+ with self.assertRaises(ScriptSerializerFromFileError):
+ ScriptSerializer().create_from_yaml('Not a YAML string')
+ ScriptSerializer(title='Script 1', script='Script text', id='12345', last_updated='2021-04-26', script_language_version='1.0')
def test_naming_script_object_1(self):
# Check initial loaded values.
- test_script = FileNamingScript(
+ test_script = FileNamingScriptInfo(
title='Script 1', script='Script text', id='12345', last_updated='2021-04-26',
description='Script description', author='Script author', script_language_version='1.0'
)