From 7bdc7d37d31985e916e1acdc8856ed3eca923e34 Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Thu, 28 Jan 2016 20:26:04 +0100
Subject: [PATCH 01/12] Introduced input_select_items
alternative and more flexibile implementation to fulfil #1723
Added test case for new input method
---
beets/ui/__init__.py | 25 ++++++++++++++++++++++
test/test_ui_init.py | 50 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 75 insertions(+)
diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py
index eaf79a0ce..d17e0b32c 100644
--- a/beets/ui/__init__.py
+++ b/beets/ui/__init__.py
@@ -367,6 +367,31 @@ def input_yn(prompt, require=False):
return sel == 'y'
+def input_select_items(prompt, items, rep):
+ """Prompts the user to use all, none or some of the items
+ Will return the list of items the user selected
+ prompt: prompt to use for all and for selective choice
+ items: full list of items
+ rep: function which represents an item to the user
+ is called with the item as argument
+ function is responsive for newline at input
+ """
+ out_items = []
+ choice = input_options(
+ ('y', 'n', 's'), False,
+ '%s (Yes/No/Selective)?' % prompt)
+ print()
+ if choice == 'y':
+ out_items = items
+ elif choice == 's':
+ for item in items:
+ rep(item)
+ if input_yn('%s (y/n)?' % prompt, True):
+ out_items.append(item)
+ print()
+ return out_items
+
+
# Human output formatting.
def human_bytes(size):
diff --git a/test/test_ui_init.py b/test/test_ui_init.py
index d692868be..6ab94f9ea 100644
--- a/test/test_ui_init.py
+++ b/test/test_ui_init.py
@@ -21,6 +21,56 @@ from test._common import unittest
from beets import ui
+class InputMethodsTest(_common.TestCase):
+ def setUp(self):
+ super(InputMethodsTest, self).setUp()
+ self.io.install()
+
+ def _print_helper(self, s):
+ print(s)
+
+ def _print_helper2(self, s, prefix):
+ print(prefix, s)
+
+ def test_input_select_items(self):
+ full_items = ['1', '2', '3', '4', '5']
+
+ # Test no
+ self.io.addinput('n')
+ items = ui.input_select_items(
+ "Prompt", full_items, self._print_helper)
+ self.assertEqual(items, [])
+
+ # Test yes
+ self.io.addinput('y')
+ items = ui.input_select_items(
+ "Prompt", full_items, self._print_helper)
+ self.assertEqual(items, full_items)
+
+ # Test selective 1
+ self.io.addinput('s')
+ self.io.addinput('n')
+ self.io.addinput('y')
+ self.io.addinput('n')
+ self.io.addinput('y')
+ self.io.addinput('n')
+ items = ui.input_select_items(
+ "Prompt", full_items, self._print_helper)
+ self.assertEqual(items, ['2', '4'])
+
+ # Test selective 2
+ self.io.addinput('s')
+ self.io.addinput('y')
+ self.io.addinput('y')
+ self.io.addinput('n')
+ self.io.addinput('y')
+ self.io.addinput('n')
+ items = ui.input_select_items(
+ "Prompt", full_items,
+ lambda s: self._print_helper2(s, "Prefix"))
+ self.assertEqual(items, ['1', '2', '4'])
+
+
class InitTest(_common.LibTestCase):
def setUp(self):
super(InitTest, self).setUp()
From 6b3975133706c06c1dd91023af7323dea508e898 Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Thu, 28 Jan 2016 21:49:33 +0100
Subject: [PATCH 02/12] introduced print_modify_item as seperate function
preparation to implement interactive selection in modify command
---
beets/ui/commands.py | 23 ++++++++++++++++-------
1 file changed, 16 insertions(+), 7 deletions(-)
diff --git a/beets/ui/commands.py b/beets/ui/commands.py
index 3e3acb7c3..176cf9218 100644
--- a/beets/ui/commands.py
+++ b/beets/ui/commands.py
@@ -1330,13 +1330,7 @@ def modify_items(lib, mods, dels, query, write, move, album, confirm):
.format(len(objs), 'album' if album else 'item'))
changed = set()
for obj in objs:
- obj.update(mods)
- for field in dels:
- try:
- del obj[field]
- except KeyError:
- pass
- if ui.show_model_changes(obj):
+ if print_modify_item(obj, mods, dels):
changed.add(obj)
# Still something to do?
@@ -1364,6 +1358,21 @@ def modify_items(lib, mods, dels, query, write, move, album, confirm):
obj.try_sync(write, move)
+def print_modify_item(obj, mods, dels):
+ """Print the modifications to an item
+ and return False if no changes were made
+ mods: modifications
+ dels: fields to delete
+ """
+ obj.update(mods)
+ for field in dels:
+ try:
+ del obj[field]
+ except KeyError:
+ pass
+ return ui.show_model_changes(obj)
+
+
def modify_parse_args(args):
"""Split the arguments for the modify subcommand into query parts,
assignments (field=value), and deletions (field!). Returns the result as
From c28eaee7d0a5a8db89356d0e0c019d124c378be7 Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Thu, 28 Jan 2016 21:50:37 +0100
Subject: [PATCH 03/12] implemented interactive selection in modify
---
beets/ui/commands.py | 5 +++--
test/test_ui.py | 16 ++++++++++++++++
2 files changed, 19 insertions(+), 2 deletions(-)
diff --git a/beets/ui/commands.py b/beets/ui/commands.py
index 176cf9218..afd998042 100644
--- a/beets/ui/commands.py
+++ b/beets/ui/commands.py
@@ -1349,8 +1349,9 @@ def modify_items(lib, mods, dels, query, write, move, album, confirm):
else:
extra = ''
- if not ui.input_yn('Really modify%s (Y/n)?' % extra):
- return
+ changed = ui.input_select_items(
+ 'Really modify%s (Y/n)?' % extra, changed,
+ lambda o: print_modify_item(o, mods, dels))
# Apply changes to database and files
with lib.transaction():
diff --git a/test/test_ui.py b/test/test_ui.py
index 4a10a7947..480e6ce03 100644
--- a/test/test_ui.py
+++ b/test/test_ui.py
@@ -231,6 +231,22 @@ class ModifyTest(unittest.TestCase, TestHelper):
item.load()
self.assertEqual(0, item.mtime)
+ def test_selective_modify(self):
+ title = "Tracktitle"
+ album = "album"
+ origArtist = "composer"
+ newArtist = "coverArtist"
+ for i in range(0, 10):
+ self.add_item_fixture(title="{0}{1}".format(title, i),
+ artist=origArtist,
+ album=album)
+ self.modify_inp('s\ny\ny\ny\nn\nn\ny\ny\ny\ny\nn',
+ title, "artist={0}".format(newArtist))
+ origItems = self.lib.items("artist:{0}".format(origArtist))
+ newItems = self.lib.items("artist:{0}".format(newArtist))
+ self.assertEqual(len(list(origItems)), 3)
+ self.assertEqual(len(list(newItems)), 7)
+
# Album Tests
def test_modify_album(self):
From 9f2d58211009c31d78e905c160ebdd52217eb43e Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Thu, 28 Jan 2016 21:55:46 +0100
Subject: [PATCH 04/12] Added changelog entry for c28eaee
---
docs/changelog.rst | 2 ++
1 file changed, 2 insertions(+)
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 5a9cd366f..199c08789 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -27,6 +27,8 @@ New:
:bug:`1778`
* :doc:`/plugins/info`: A new option will print only fields' names and not
their values. Thanks to :user:`GuilhermeHideki`. :bug:`1812`
+* The :ref:`modify-cmd` command lets you interactive select tracks to apply
+ changes. :bug:`1843`
.. _Google Code-In: https://codein.withgoogle.com/
.. _AcousticBrainz: http://acousticbrainz.org/
From 2937607d529d1227f3461b0e674b244bf9357512 Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Thu, 28 Jan 2016 22:09:56 +0100
Subject: [PATCH 05/12] Removed duplicate (Y/n) Question from output
---
beets/ui/commands.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/beets/ui/commands.py b/beets/ui/commands.py
index afd998042..d5aeefe2f 100644
--- a/beets/ui/commands.py
+++ b/beets/ui/commands.py
@@ -1350,7 +1350,7 @@ def modify_items(lib, mods, dels, query, write, move, album, confirm):
extra = ''
changed = ui.input_select_items(
- 'Really modify%s (Y/n)?' % extra, changed,
+ 'Really modify%s' % extra, changed,
lambda o: print_modify_item(o, mods, dels))
# Apply changes to database and files
From 4863a945a282479e5b3f92931063a940a1c71922 Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Thu, 28 Jan 2016 22:22:21 +0100
Subject: [PATCH 06/12] introduced -t option for move command
---
beets/ui/commands.py | 19 ++++++++++++++++---
docs/changelog.rst | 2 ++
2 files changed, 18 insertions(+), 3 deletions(-)
diff --git a/beets/ui/commands.py b/beets/ui/commands.py
index d5aeefe2f..1e9daff98 100644
--- a/beets/ui/commands.py
+++ b/beets/ui/commands.py
@@ -1432,7 +1432,7 @@ default_commands.append(modify_cmd)
# move: Move/copy files to the library or a new base directory.
-def move_items(lib, dest, query, copy, album, pretend):
+def move_items(lib, dest, query, copy, album, pretend, confirm):
"""Moves or copies items to a new base directory, given by dest. If
dest is None, then the library's base directory is used, making the
command "consolidate" files.
@@ -1446,6 +1446,7 @@ def move_items(lib, dest, query, copy, album, pretend):
objs = [o for o in objs if (isalbummoved if album else isitemmoved)(o)]
action = 'Copying' if copy else 'Moving'
+ act = 'copy' if copy else 'move'
entity = 'album' if album else 'item'
log.info(u'{0} {1} {2}{3}.', action, len(objs), entity,
's' if len(objs) != 1 else '')
@@ -1460,6 +1461,12 @@ def move_items(lib, dest, query, copy, album, pretend):
show_path_changes([(obj.path, obj.destination(basedir=dest))
for obj in objs])
else:
+ if confirm:
+ objs = ui.input_select_items(
+ 'Really %s' % act, objs,
+ lambda o: show_path_changes(
+ [(o.path, o.destination(basedir=dest))]))
+
for obj in objs:
log.debug(u'moving: {0}', util.displayable_path(obj.path))
@@ -1474,7 +1481,8 @@ def move_func(lib, opts, args):
if not os.path.isdir(dest):
raise ui.UserError('no such directory: %s' % dest)
- move_items(lib, dest, decargs(args), opts.copy, opts.album, opts.pretend)
+ move_items(lib, dest, decargs(args), opts.copy, opts.album, opts.pretend,
+ opts.timid)
move_cmd = ui.Subcommand(
@@ -1490,7 +1498,12 @@ move_cmd.parser.add_option(
)
move_cmd.parser.add_option(
'-p', '--pretend', default=False, action='store_true',
- help='show how files would be moved, but don\'t touch anything')
+ help='show how files would be moved, but don\'t touch anything'
+)
+move_cmd.parser.add_option(
+ '-t', '--timid', dest='timid', action='store_true',
+ help='always confirm all actions'
+)
move_cmd.parser.add_album_option()
move_cmd.func = move_func
default_commands.append(move_cmd)
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 199c08789..d8ea34540 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -29,6 +29,8 @@ New:
their values. Thanks to :user:`GuilhermeHideki`. :bug:`1812`
* The :ref:`modify-cmd` command lets you interactive select tracks to apply
changes. :bug:`1843`
+* The :ref:`move-cmd` command accepts `-t`, `--timid` switch now to confirm
+ or interactive select tracks process. :bug:`1843`
.. _Google Code-In: https://codein.withgoogle.com/
.. _AcousticBrainz: http://acousticbrainz.org/
From fafc73077da55fb663c7a49aad19083d7493a4c7 Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Thu, 28 Jan 2016 22:26:55 +0100
Subject: [PATCH 07/12] fixed broken tests
---
beets/ui/commands.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/beets/ui/commands.py b/beets/ui/commands.py
index 1e9daff98..244d871c7 100644
--- a/beets/ui/commands.py
+++ b/beets/ui/commands.py
@@ -1432,7 +1432,7 @@ default_commands.append(modify_cmd)
# move: Move/copy files to the library or a new base directory.
-def move_items(lib, dest, query, copy, album, pretend, confirm):
+def move_items(lib, dest, query, copy, album, pretend, confirm=False):
"""Moves or copies items to a new base directory, given by dest. If
dest is None, then the library's base directory is used, making the
command "consolidate" files.
From 5747c6ae68e59af55fe01d7351836c2522d44e41 Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Fri, 29 Jan 2016 16:19:31 +0100
Subject: [PATCH 08/12] changed "Selecive" to "Select items" in ui
---
beets/ui/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py
index d17e0b32c..1ff7da562 100644
--- a/beets/ui/__init__.py
+++ b/beets/ui/__init__.py
@@ -379,7 +379,7 @@ def input_select_items(prompt, items, rep):
out_items = []
choice = input_options(
('y', 'n', 's'), False,
- '%s (Yes/No/Selective)?' % prompt)
+ '%s (Yes/No/Select items)?' % prompt)
print()
if choice == 'y':
out_items = items
From 34e455b2ea454b397ab0509037a91e8b90a70706 Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Fri, 29 Jan 2016 16:21:39 +0100
Subject: [PATCH 09/12] changed docstring for print_modify_item
---
beets/ui/commands.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/beets/ui/commands.py b/beets/ui/commands.py
index 244d871c7..69e4c2e0b 100644
--- a/beets/ui/commands.py
+++ b/beets/ui/commands.py
@@ -1361,7 +1361,7 @@ def modify_items(lib, mods, dels, query, write, move, album, confirm):
def print_modify_item(obj, mods, dels):
"""Print the modifications to an item
- and return False if no changes were made
+ and return a bool indicating whether any changes were made
mods: modifications
dels: fields to delete
"""
From 8412bd0f23d3f11d703a820d22e936aa035a3b35 Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Sat, 30 Jan 2016 09:45:50 +0100
Subject: [PATCH 10/12] renamed print_modify_item to print_and_modify
---
beets/ui/commands.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/beets/ui/commands.py b/beets/ui/commands.py
index 69e4c2e0b..d1d641b15 100644
--- a/beets/ui/commands.py
+++ b/beets/ui/commands.py
@@ -1330,7 +1330,7 @@ def modify_items(lib, mods, dels, query, write, move, album, confirm):
.format(len(objs), 'album' if album else 'item'))
changed = set()
for obj in objs:
- if print_modify_item(obj, mods, dels):
+ if print_and_modify(obj, mods, dels):
changed.add(obj)
# Still something to do?
@@ -1351,7 +1351,7 @@ def modify_items(lib, mods, dels, query, write, move, album, confirm):
changed = ui.input_select_items(
'Really modify%s' % extra, changed,
- lambda o: print_modify_item(o, mods, dels))
+ lambda o: print_and_modify(o, mods, dels))
# Apply changes to database and files
with lib.transaction():
@@ -1359,7 +1359,7 @@ def modify_items(lib, mods, dels, query, write, move, album, confirm):
obj.try_sync(write, move)
-def print_modify_item(obj, mods, dels):
+def print_and_modify(obj, mods, dels):
"""Print the modifications to an item
and return a bool indicating whether any changes were made
mods: modifications
From f550dfecf1283e1099795572db788adf5fd224b6 Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Wed, 3 Feb 2016 18:43:26 +0100
Subject: [PATCH 11/12] changed prompt for input_select_items
moved question mark and removed word "item"
---
beets/ui/__init__.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py
index 1ff7da562..5e3835825 100644
--- a/beets/ui/__init__.py
+++ b/beets/ui/__init__.py
@@ -379,14 +379,14 @@ def input_select_items(prompt, items, rep):
out_items = []
choice = input_options(
('y', 'n', 's'), False,
- '%s (Yes/No/Select items)?' % prompt)
+ '%s? (Yes/no/select)' % prompt)
print()
if choice == 'y':
out_items = items
elif choice == 's':
for item in items:
rep(item)
- if input_yn('%s (y/n)?' % prompt, True):
+ if input_yn('%s? (yes/no)' % prompt, True):
out_items.append(item)
print()
return out_items
From 89d38caea6368fcc802e18e719358134b131458a Mon Sep 17 00:00:00 2001
From: Peter Kessen
Date: Wed, 3 Feb 2016 19:14:12 +0100
Subject: [PATCH 12/12] added comment
---
beets/ui/__init__.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py
index 5e3835825..0d018d1e7 100644
--- a/beets/ui/__init__.py
+++ b/beets/ui/__init__.py
@@ -380,7 +380,7 @@ def input_select_items(prompt, items, rep):
choice = input_options(
('y', 'n', 's'), False,
'%s? (Yes/no/select)' % prompt)
- print()
+ print() # go to a new line
if choice == 'y':
out_items = items
elif choice == 's':
@@ -388,7 +388,7 @@ def input_select_items(prompt, items, rep):
rep(item)
if input_yn('%s? (yes/no)' % prompt, True):
out_items.append(item)
- print()
+ print() # go to a new line
return out_items