From 8303c71b3835ed886826ec976e6e84a73496dbe2 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Sat, 22 Feb 2014 00:14:18 -0500 Subject: [PATCH] robust difference display for `modify` This new alternative to _showdiff takes care of formatting and is better at highlighting differences for non-string fields. This takes care of the issue where "True -> False" would have everything but the "e" highlighted. --- beets/ui/commands.py | 72 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 14 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 88c09a010..8405e8969 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -22,7 +22,6 @@ import os import time import itertools import codecs -from datetime import datetime import beets from beets import ui @@ -46,8 +45,10 @@ log = logging.getLogger('beets') default_commands = [] + # Utilities. + def _do_query(lib, query, album, also_items=True): """For commands that operate on matched items, performs a query and returns a list of matching items and a list of matching @@ -73,6 +74,7 @@ def _do_query(lib, query, album, also_items=True): return items, albums + FLOAT_EPSILON = 0.01 def _showdiff(field, oldval, newval): """Print out a human-readable field difference line if `oldval` and @@ -93,6 +95,54 @@ def _showdiff(field, oldval, newval): return False +def _field_diff(field, old, new): + """Given two Model objects, format their values for `field` and + highlight changes among them. Return a human-readable string. If the + value has not changed, return None instead. + """ + oldval = old.get(field) + newval = new.get(field) + + # If no change, abort. + if isinstance(oldval, float) and isinstance(newval, float) and \ + abs(oldval - newval) < FLOAT_EPSILON: + return None + elif oldval == newval: + return None + + # Get formatted values for output. + oldstr = old._get_formatted(field) + newstr = new._get_formatted(field) + + # For strings, highlight changes. For others, colorize the whole + # thing. + if isinstance(oldval, basestring): + oldstr, newstr = ui.colordiff(oldval, newval) + else: + oldstr, newstr = ui.colorize('red', oldstr), ui.colorize('red', newstr) + + return u'{0} -> {1}'.format(oldstr, newstr) + + +def _show_model_changes(new, old=None): + """Given a Model object, print a list of changes from its pristine + version stored in the database. Return a boolean indicating whether + any changes were found. + """ + old = old or new._db._get(type(new), new.id) + ui.print_obj(old, old._db) + + changed = False + for field in old: + line = _field_diff(field, old, new) + if line: + print_(u' ' + line) + changed = True + + return changed + + + # fields: Shows a list of available fields for queries and format strings. fields_cmd = ui.Subcommand('fields', @@ -472,8 +522,8 @@ def choose_candidate(candidates, singleton, rec, cur_artist=None, .format(itemcount)) print_('For help, see: ' 'http://beets.readthedocs.org/en/latest/faq.html#nomatch') - opts = ('Use as-is', 'as Tracks', 'Group albums', 'Skip', 'Enter search', - 'enter Id', 'aBort') + opts = ('Use as-is', 'as Tracks', 'Group albums', 'Skip', + 'Enter search', 'enter Id', 'aBort') sel = ui.input_options(opts) if sel == 'u': return importer.action.ASIS @@ -1119,18 +1169,15 @@ def modify_items(lib, mods, query, write, move, album, confirm): items, albums = _do_query(lib, query, album, False) objs = albums if album else items - # Preview change and collect modified objects. + # Apply changes *temporarily*, preview them, and collect modified + # objects. print_('Modifying %i %ss.' % (len(objs), 'album' if album else 'item')) changed = set() for obj in objs: - # Identify the changed object. - ui.print_obj(obj, lib) - - # Show each change. for field, value in fsets.iteritems(): - if _showdiff(field, obj._get_formatted(field), - obj._format(field, value)): - changed.add(obj) + obj[field] = value + if _show_model_changes(obj): + changed.add(obj) # Still something to do? if not changed: @@ -1146,9 +1193,6 @@ def modify_items(lib, mods, query, write, move, album, confirm): # Apply changes to database. with lib.transaction(): for obj in changed: - for field, value in fsets.iteritems(): - obj[field] = value - if move: cur_path = obj.path if lib.directory in ancestry(cur_path): # In library?