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