From a9ed100012f61c7fe1983989a42eb9992e0c81b2 Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Sun, 1 Nov 2015 17:25:27 +0100 Subject: [PATCH 01/45] UI: Add more ANSI colors - Add ANSI styles, colors and background-colors - Modify colorize functions to adopt new colors - Modify default configuration to adopt new color scheme: - Define colors by arrays with their ANSI codes as strings - Deprecated DARK_COLORS and LIGHT_COLORS --- beets/config_default.yaml | 27 ++++++++++++----- beets/ui/__init__.py | 61 +++++++++++++++++++++++++++++++++++---- 2 files changed, 75 insertions(+), 13 deletions(-) diff --git a/beets/config_default.yaml b/beets/config_default.yaml index f708702a81..74b72f84b3 100644 --- a/beets/config_default.yaml +++ b/beets/config_default.yaml @@ -53,13 +53,26 @@ ui: length_diff_thresh: 10.0 color: yes colors: - text_success: green - text_warning: yellow - text_error: red - text_highlight: red - text_highlight_minor: lightgray - action_default: turquoise - action: blue + text_success: ['bold', 'green'] + text_warning: ['bold', 'yellow'] + text_error: ['bold', 'red'] + text_highlight: ['bold', 'red'] + text_highlight_minor: ['white'] + action_default: ['bold', 'cyan'] + action: ['bold', 'blue'] + # New Colors + import_path: ['bold', 'inverse', 'blue'] + import_path_items: ['bold', 'blue'] + added: ['green'] + removed: ['red'] + changed: ['yellow'] + added_highlight: ['bold', 'green'] + removed_highlight: ['bold', 'red'] + changed_highlight: ['bold', 'yellow'] + text_diff_added: ['bold', 'red'] + text_diff_removed: ['bold', 'red'] + text_diff_changed: ['bold', 'red'] + action_description: ['blue'] format_item: $artist - $album - $title format_album: $albumartist - $album diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 768eb76c78..3c54dcb76d 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -403,12 +403,50 @@ def human_seconds_short(interval): "cyan": 6, "white": 7 } +# All ANSI Colors. +ANSI_CODES = { + # Styles. + "normal": 0, + "bold": 1, + "faint": 2, + #"italic": 3, + "underline": 4, + #"blink_slow": 5, + #"blink_rapid": 6, + "inverse": 7, + #"conceal": 8, + #"crossed_out": 9 + # Text colors. + "black": 30, + "red": 31, + "green": 32, + "yellow": 33, + "blue": 34, + "magenta": 35, + "cyan": 36, + "white": 37, + # Background colors. + "bg_black": 40, + "bg_red": 41, + "bg_green": 42, + "bg_yellow": 43, + "bg_blue": 44, + "bg_magenta": 45, + "bg_cyan": 46, + "bg_white": 47 +} RESET_COLOR = COLOR_ESCAPE + "39;49;00m" # These abstract COLOR_NAMES are lazily mapped on to the actual color in COLORS # as they are defined in the configuration files, see function: colorize COLOR_NAMES = ['text_success', 'text_warning', 'text_error', 'text_highlight', - 'text_highlight_minor', 'action_default', 'action'] + 'text_highlight_minor', 'action_default', 'action', + # New Colors + 'import_path', 'import_path_items', + 'action_description', + 'added', 'removed', 'changed', + 'added_highlight', 'removed_highlight', 'changed_highlight', + 'added_diff', 'removed_diff', 'changed_diff'] COLORS = None @@ -417,10 +455,19 @@ def _colorize(color, text): in a terminal that is ANSI color-aware. The color must be something in DARK_COLORS or LIGHT_COLORS. """ - if color in DARK_COLORS: - escape = COLOR_ESCAPE + "%im" % (DARK_COLORS[color] + 30) - elif color in LIGHT_COLORS: - escape = COLOR_ESCAPE + "%i;01m" % (LIGHT_COLORS[color] + 30) + if not isinstance(color, basestring): + # Non-strings are lists with advanced color definitions + escape = "" + for color_def in color: + + color_def = "{0}".format(color_def) # TODO what the fuck + + if color_def in ANSI_CODES.keys(): + escape = escape + COLOR_ESCAPE + "%im" % ANSI_CODES[color_def] + #elif color_def in DARK_COLORS: + # escape = COLOR_ESCAPE + "%im" % (DARK_COLORS[color_def] + 30) + #elif color_def in LIGHT_COLORS: + # escape = COLOR_ESCAPE + "%i;01m" % (LIGHT_COLORS[color_def] + 30) else: raise ValueError('no such color %s', color) return escape + text + RESET_COLOR @@ -433,7 +480,9 @@ def colorize(color_name, text): if config['ui']['color']: global COLORS if not COLORS: - COLORS = dict((name, config['ui']['colors'][name].get(unicode)) + # TODO uncomment and repair + #COLORS = dict((name, config['ui']['colors'][name].get(unicode)) + COLORS = dict((name, config['ui']['colors'][name]) for name in COLOR_NAMES) # In case a 3rd party plugin is still passing the actual color ('red') # instead of the abstract color name ('text_error') From 1cf39db5388fe8ba15c29278b55340d77c594051 Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Sun, 1 Nov 2015 19:09:22 +0100 Subject: [PATCH 02/45] UI: Add ui.indent() method for easy indentation and alignment --- beets/ui/__init__.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 3c54dcb76d..782bab2633 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -137,6 +137,18 @@ def print_(*strings, **kwargs): sys.stdout.write(txt) +def indent(count): + """Indents string with spaces. + """ + return u' ' * count + + +def indent_str(count, string): + """Indents string with spaces. + """ + return indent(count) + string + + def input_(prompt=None): """Like `raw_input`, but decodes the result to a Unicode string. Raises a UserError if stdin is not available. The prompt is sent to From 6bb68983ab79b50f913461f666b888606a162137 Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Sun, 1 Nov 2015 19:10:07 +0100 Subject: [PATCH 03/45] UI: Colorize prompt --- beets/ui/__init__.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 782bab2633..0ca8ea8cd0 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -232,8 +232,11 @@ def input_options(options, require=False, prompt=None, fallback_prompt=None, show_letter) # Insert the highlighted letter back into the word. + descr_color = 'action_default' if is_default else 'action_description' capitalized.append( - option[:index] + show_letter + option[index + 1:] + colorize(descr_color, option[:index]) + + show_letter + + colorize(descr_color, option[index + 1:]) ) display_letters.append(found_letter.upper()) @@ -266,15 +269,16 @@ def input_options(options, require=False, prompt=None, fallback_prompt=None, prompt_part_lengths += [len(s) for s in options] # Wrap the query text. - prompt = '' + # Start prompt with U+279C: Heavy Round-Tipped Rightwards Arrow + prompt = colorize('action', '\u279C ') line_length = 0 for i, (part, length) in enumerate(zip(prompt_parts, prompt_part_lengths)): # Add punctuation. if i == len(prompt_parts) - 1: - part += '?' + part += colorize('action_description', '?') else: - part += ',' + part += colorize('action_description', ',') length += 1 # Choose either the current line or the beginning of the next. @@ -333,8 +337,11 @@ def input_yn(prompt, require=False): """Prompts the user for a "yes" or "no" response. The default is "yes" unless `require` is `True`, in which case there is no default. """ + # Start prompt with U+279C: Heavy Round-Tipped Rightwards Arrow + yesno = colorize('action', '\u279C ') + \ + colorize('action_description', 'Enter Y or N:') sel = input_options( - ('y', 'n'), require, prompt, 'Enter Y or N:' + ('y', 'n'), require, prompt, yesno ) return sel == 'y' From f43a5c8b56782d37446065ecec4959f57d9f9752 Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Sun, 1 Nov 2015 19:11:04 +0100 Subject: [PATCH 04/45] UI: fixup color names --- 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 0ca8ea8cd0..f217bcb021 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -465,7 +465,7 @@ def human_seconds_short(interval): 'action_description', 'added', 'removed', 'changed', 'added_highlight', 'removed_highlight', 'changed_highlight', - 'added_diff', 'removed_diff', 'changed_diff'] + 'text_diff_added', 'text_diff_removed', 'text_diff_changed'] COLORS = None From 6b1f2157383ef7d86c30f0a78d7485e900cc1ddc Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Sun, 1 Nov 2015 19:13:17 +0100 Subject: [PATCH 05/45] UI: place ANSI codes at word borders in _colordiff - when linebreaking, ANSI codes should end at word borders - this removes change markers for whitespace! --- beets/ui/__init__.py | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index f217bcb021..ee863b9808 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -546,19 +546,37 @@ def _colordiff(a, b, highlight='text_highlight', b_out.append(b[b_start:b_end]) elif op == 'insert': # Right only. - b_out.append(colorize(highlight, b[b_start:b_end])) + words = re.split('(\s)', b[b_start:b_end]) + mapper = lambda w: \ + w if re.match('(\s)', w) else colorize('text_diff_added', w) + words_colorized = map(mapper, words) + b_out.append(''.join(words_colorized)) elif op == 'delete': # Left only. - a_out.append(colorize(highlight, a[a_start:a_end])) + words = re.split('(\s)', a[a_start:a_end]) + mapper = lambda w: \ + w if re.match('(\s)', w) else colorize('text_diff_removed', w) + words_colorized = map(mapper, words) + a_out.append(''.join(words_colorized)) elif op == 'replace': # Right and left differ. Colorise with second highlight if # it's just a case change. if a[a_start:a_end].lower() != b[b_start:b_end].lower(): - color = highlight + color_a = 'text_diff_removed' + color_b = 'text_diff_added' else: - color = minor_highlight - a_out.append(colorize(color, a[a_start:a_end])) - b_out.append(colorize(color, b[b_start:b_end])) + color_a = minor_highlight + color_b = minor_highlight + words_a = re.split('(\s)', a[a_start:a_end]) + words_b = re.split('(\s)', b[b_start:b_end]) + mapper_a = lambda w: \ + w if re.match('(\s)', w) else colorize(color_a, w) + mapper_b = lambda w: \ + w if re.match('(\s)', w) else colorize(color_b, w) + words_a_colorized = map(mapper_a, words_a) + words_b_colorized = map(mapper_b, words_b) + a_out.append(''.join(words_a_colorized)) + b_out.append(''.join(words_b_colorized)) else: assert(False) From 6a556c95d16f0f995050e027da3a3d9e8a4a6a4b Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Sun, 1 Nov 2015 19:14:16 +0100 Subject: [PATCH 06/45] UI: Separate disambiguation elements with pipe --- 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 348d12c887..2d69da9563 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -152,7 +152,7 @@ def disambig_string(info): disambig.append(info.albumdisambig) if disambig: - return u', '.join(disambig) + return u' | '.join(disambig) def dist_string(dist): From daf635a96b564ef86ac8d5df31f7ef45cd95a18f Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Sun, 1 Nov 2015 19:16:09 +0100 Subject: [PATCH 07/45] UI: Add dist_colorize function - colors arbitrary strings according to a distance - refactor dest_string --- beets/ui/commands.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 2d69da9563..60b86a8773 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -155,18 +155,24 @@ def disambig_string(info): return u' | '.join(disambig) -def dist_string(dist): - """Formats a distance (a float) as a colorized similarity percentage - string. +def dist_colorize(string, dist): + """Formats a string as a colorized similarity string accoring to a distance. """ - out = '%.1f%%' % ((1 - dist) * 100) if dist <= config['match']['strong_rec_thresh'].as_number(): - out = ui.colorize('text_success', out) + string = ui.colorize('text_success', string) elif dist <= config['match']['medium_rec_thresh'].as_number(): - out = ui.colorize('text_warning', out) + string = ui.colorize('text_warning', string) else: - out = ui.colorize('text_error', out) - return out + string = ui.colorize('text_error', string) + return string + + +def dist_string(dist): + """Formats a distance (a float) as a colorized similarity percentage + string. + """ + string = '%.1f%%' % ((1 - dist) * 100) + return dist_colorize(string, dist) def penalty_string(distance, limit=None): From 238efe525c477fb19b35ace94c2498e36ea6acd0 Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Sun, 1 Nov 2015 19:17:15 +0100 Subject: [PATCH 08/45] UI: Prefix penalties string with NOT EQUAL TO sign --- beets/ui/commands.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 60b86a8773..b5c230ab90 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -188,7 +188,9 @@ def penalty_string(distance, limit=None): if penalties: if limit and len(penalties) > limit: penalties = penalties[:limit] + ['...'] - return ui.colorize('text_warning', '(%s)' % ', '.join(penalties)) + # Prefix penalty string with U+2260: Not Equal To + penalty_string = u'\u2260 %s' % ', '.join(penalties) + return ui.colorize('changed', penalty_string) def show_change(cur_artist, cur_album, match): From c541ec822960f8ddcb6ba83e32f6a5776bbdf8bc Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Sun, 1 Nov 2015 19:18:27 +0100 Subject: [PATCH 09/45] UI: Change show_change header style - more concise format --- beets/ui/commands.py | 65 ++++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index b5c230ab90..6a090387af 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -229,42 +229,49 @@ def format_index(track_info): return unicode(index) # Identify the album in question. - if cur_artist != match.info.artist or \ - (cur_album != match.info.album and - match.info.album != VARIOUS_ARTISTS): - artist_l, artist_r = cur_artist or '', match.info.artist - album_l, album_r = cur_album or '', match.info.album - if artist_r == VARIOUS_ARTISTS: - # Hide artists for VA releases. - artist_l, artist_r = u'', u'' + # 'Match' header and similarity. + print_('') + print_(ui.indent(2) + u'Match: (%s):' % dist_string(match.distance)) - artist_l, artist_r = ui.colordiff(artist_l, artist_r) - album_l, album_r = ui.colordiff(album_l, album_r) + # Artist name and album title. + artist_album_str = u"{0.artist} - {0.album}".format(match.info) + print_(ui.indent(2) + dist_colorize(artist_album_str, match.distance)) - print_("Correcting tags from:") - show_album(artist_l, album_l) - print_("To:") - show_album(artist_r, album_r) - else: - print_(u"Tagging:\n {0.artist} - {0.album}".format(match.info)) - - # Data URL. - if match.info.data_url: - print_('URL:\n %s' % match.info.data_url) - - # Info line. - info = [] - # Similarity. - info.append('(Similarity: %s)' % dist_string(match.distance)) # Penalties. penalties = penalty_string(match.distance) if penalties: - info.append(penalties) - # Disambiguation. + print_(ui.indent(2) + penalties) + + # Disambiguation disambig = disambig_string(match.info) if disambig: - info.append(ui.colorize('text_highlight_minor', '(%s)' % disambig)) - print_(' '.join(info)) + print_(ui.indent(2) + ui.colorize('text_highlight_minor', disambig)) + + # Data URL. + if match.info.data_url: + url = ui.colorize('text_highlight_minor', '%s' % match.info.data_url) + print_(ui.indent(2) + url) + + # Artist. + artist_l, artist_r = cur_artist or '', match.info.artist + if artist_r == VARIOUS_ARTISTS: + # Hide artists for VA releases. + artist_l, artist_r = u'', u'' + if artist_l != artist_r: + artist_l, artist_r = ui.colordiff(artist_l, artist_r) + # Prefix with U+2260: Not Equal To + print_(ui.indent(2) + ui.colorize('changed', u'\u2260'), u'Artist:', artist_l, u'->', artist_r) + else: + print_(ui.indent(2) + '=', 'Artist:', artist_r) + + # Album + album_l, album_r = cur_album or '', match.info.album + if (cur_album != match.info.album and match.info.album != VARIOUS_ARTISTS): + album_l, album_r = ui.colordiff(album_l, album_r) + # Prefix with U+2260: Not Equal To + print_(ui.indent(2) + ui.colorize('changed', u'\u2260'), u'Album:', album_l, u'->', album_r) + else: + print_(ui.indent(2) + '=', 'Album:', album_r) # Tracks. pairs = match.mapping.items() From d70802286c69457be53dc239c0ed6ffc3c06a5cc Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Sun, 1 Nov 2015 19:23:55 +0100 Subject: [PATCH 10/45] UI: Change candidate selection view --- beets/ui/commands.py | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 6a090387af..ff7c7581b8 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -547,36 +547,42 @@ def choose_candidate(candidates, singleton, rec, cur_artist=None, if not bypass_candidates: # Display list of candidates. + print_(u'') print_(u'Finding tags for {0} "{1} - {2}".'.format( u'track' if singleton else u'album', item.artist if singleton else cur_artist, item.title if singleton else cur_album, )) - print_(u'Candidates:') + print_(ui.indent(2) + u'Candidates:') for i, match in enumerate(candidates): # Index, metadata, and distance. - line = [ - u'{0}.'.format(i + 1), - u'{0} - {1}'.format( - match.info.artist, - match.info.title if singleton else match.info.album, - ), - u'({0})'.format(dist_string(match.distance)), + index0 = u'{0}.'.format(i + 1) + index = dist_colorize(index0, match.distance) + dist = '(%.1f%%)' % ((1 - match.distance) * 100) + distance = dist_colorize(dist, match.distance) + metadata = u'{0} - {1}'.format( + match.info.artist, + match.info.title if singleton else match.info.album, + ) + if i == 0: + metadata = dist_colorize(metadata, match.distance) + line1 = [ + index, + distance, + metadata ] + print_(ui.indent(2) + ' '.join(line1)) # Penalties. penalties = penalty_string(match.distance, 3) if penalties: - line.append(penalties) + print_(ui.indent(13) + penalties) # Disambiguation disambig = disambig_string(match.info) if disambig: - line.append(ui.colorize('text_highlight_minor', - '(%s)' % disambig)) - - print_(' '.join(line)) + print_(ui.indent(13) + ui.colorize('text_highlight_minor', disambig)) # Ask the user for a choice. if singleton: From e9caa7a6f7ae7d69fee586a53c11c6257d6a49bb Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Sun, 1 Nov 2015 19:25:19 +0100 Subject: [PATCH 11/45] UI: Apply ANSI styles to import task path string --- beets/ui/commands.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index ff7c7581b8..c508cd64e1 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -689,8 +689,11 @@ def choose_match(self, task): """ # Show what we're tagging. print_() - print_(displayable_path(task.paths, u'\n') + - u' ({0} items)'.format(len(task.items))) + path_str0 = displayable_path(task.paths, u'\n') + path_str = ui.colorize('import_path', path_str0) + items_str0 = u'({0} items)'.format(len(task.items)) + items_str = ui.colorize('import_path_items', items_str0) + print_(' '.join([path_str, items_str])) # Take immediate action if appropriate. action = _summary_judment(task.rec) From 42f55b1bbacf0b3959f3b45b33d0e84c06179cec Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Sun, 1 Nov 2015 21:17:41 +0100 Subject: [PATCH 12/45] UI: Implement two layouts for track changes --- beets/config_default.yaml | 3 + beets/ui/__init__.py | 63 +++++++++ beets/ui/commands.py | 266 ++++++++++++++++++++++++++++++++++---- 3 files changed, 304 insertions(+), 28 deletions(-) diff --git a/beets/config_default.yaml b/beets/config_default.yaml index 74b72f84b3..51ab224598 100644 --- a/beets/config_default.yaml +++ b/beets/config_default.yaml @@ -73,6 +73,9 @@ ui: text_diff_removed: ['bold', 'red'] text_diff_changed: ['bold', 'red'] action_description: ['blue'] + import: + albumdiff: + layout: newline format_item: $artist - $album - $title format_album: $albumartist - $album diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index ee863b9808..bced051c98 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -645,6 +645,69 @@ def term_width(): return width +def split_into_lines(string, raw_string, first_width, middle_width, last_width): + """Splits string into substrings at whitespace. The first substring has a + length not longer than first_width, the last substring has a length not + longer than last_width, and all other substrings have a length not longer + than middle_width. + if raw_string is defined, raw_string and string contain the same words, but + string contains ANSI codes at word borders. Use raw_string to find + substrings, but return the words of string. + """ + #print_('str: {}\nraw: {}\nfw: {}\nmw: {}\nlw: {}'.format(string, raw_string, first_width, middle_width, last_width)) + words_raw = raw_string.split() + words = string.split() + assert len(words_raw) == len(words) + result = { 'col': [], 'raw': [] } + next_substr_raw = u'' + next_substr = u'' + # Iterate over all words. + for i in range(len(words_raw)): + if i == 0: + pot_substr_raw = words_raw[i] + pot_substr = words[i] + else: + pot_substr_raw = ' '.join([next_substr_raw, words_raw[i]]) + pot_substr = ' '.join([next_substr, words[i]]) + + #print_('pot_substr_raw: {}'.format(pot_substr_raw)) + + # Find out if pot(ential)_substr fits into next substring + fits_first = \ + (len(result['raw']) == 0 and len(pot_substr_raw) <= first_width) + fits_middle = \ + (len(result['raw']) != 0 and len(pot_substr_raw) <= middle_width) + if fits_first or fits_middle: + next_substr_raw = pot_substr_raw + next_substr = pot_substr + else: + result['raw'].append(next_substr_raw) + result['col'].append(next_substr) + next_substr_raw = words_raw[i] + next_substr = words[i] + # Assure that last line fits. + if len(next_substr_raw) <= last_width: + result['raw'].append(next_substr_raw) + result['col'].append(next_substr) + else: + words_raw = next_substr_raw.split() + words = next_substr.split() + assert len(words_raw) == len(words) + if len(words_raw) > 1: + last_substr_raw = words_raw.pop() + last_substr = words.pop() + next_substr_raw = ' '.join(words_raw) + next_substr = ' '.join(words) + else: + last_substr_raw = u'' + last_substr = u'' + result['raw'].append(next_substr_raw) + result['col'].append(next_substr) + result['raw'].append(last_substr_raw) + result['col'].append(last_substr) + return result + + FLOAT_EPSILON = 0.01 diff --git a/beets/ui/commands.py b/beets/ui/commands.py index c508cd64e1..5165a75d91 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -228,6 +228,127 @@ def format_index(track_info): else: return unicode(index) + def format_track(indent, prefix, lhs_width, rhs_width, col_width_l, col_width_r, lhs, rhs): + """docstring for format_track""" + # Print track + pad_l = ' ' * (col_width_l - lhs_width) + pad_r = ' ' * (col_width_r - rhs_width) + template = "{0} {1} {2}{3}" + lhs_str = template.format( + lhs['track'], lhs['title'], pad_l, lhs['length']) + rhs_str = template.format( + rhs['track'], rhs['title'], pad_r, rhs['length']) + print_(u'{0}{1} ->\n{2}{3}'.format(indent + prefix, lhs_str, indent + ui.indent(len(' * ')), rhs_str)) + + def format_track_as_columns(indent, prefix, col_width_l, col_width_r, + lhs, rhs): + """docstring for format_track_as_columns""" + # Calculate available space for word wrapping. + # Left-hand side. + lhs_track_len = len(lhs['raw']['track']) + lhs_length_len = len(lhs['raw']['length']) + lhs_spaces_first = 2 + lhs_used_first = lhs_track_len + lhs_spaces_first + lhs_length_len + col_width_l_first = col_width_l - lhs_used_first + lhs_spaces_middle = 1 + lhs_used_middle = lhs_track_len + lhs_spaces_middle + col_width_l_middle = col_width_l - lhs_used_middle + lhs_spaces_last = 1 + lhs_used_last = lhs_track_len + lhs_spaces_last + col_width_l_last = col_width_l - lhs_used_last + # Right-hand side. + rhs_track_len = len(rhs['raw']['track']) + rhs_length_len = len(rhs['raw']['length']) + rhs_spaces_first = 2 + rhs_used_first = rhs_track_len + rhs_spaces_first + rhs_length_len + col_width_r_first = col_width_r - rhs_used_first + rhs_spaces_middle = 1 + rhs_used_middle = rhs_track_len + rhs_spaces_middle + col_width_r_middle = col_width_r - rhs_used_middle + rhs_spaces_last = 1 + rhs_used_last = rhs_track_len + rhs_spaces_last + col_width_r_last = col_width_r - rhs_used_last + + # Calculate word wrapping. + lhs_lines = ui.split_into_lines(lhs['title'], lhs['raw']['title'], + col_width_l_first, col_width_l_middle, col_width_l_last) + rhs_lines = ui.split_into_lines(rhs['title'], rhs['raw']['title'], + col_width_r_first, col_width_r_middle, col_width_r_last) + + # Construct string for all lines of both columns. + max_line_count = max(len(lhs_lines['col']), len(rhs_lines['col'])) + align_track = len(lhs['raw']['track']) + 1 + align_length_l = len(lhs['raw']['length']) + align_length_r = len(rhs['raw']['length']) + out = u'' + for i in range(max_line_count): + # Indentation + out += indent + + # Prefix and track number, or alignment. + if i == 0: + out += prefix + out += lhs['track'] + ' ' + else: + out += ui.indent(len(' * ')) + out += ' ' * align_track + + # Line i of lhs track title. + if i in range(len(lhs_lines['col'])): + out += lhs_lines['col'][i] + + # Alignment up to the end of the left column. + if i in range(len(lhs_lines['raw'])): + align_title = len(lhs_lines['raw'][i]) + else: + align_title = 0 + align_used = align_track + align_title + if i == 0: + align_used += align_length_l + padding = col_width_l - align_used + out += ' ' * padding + + # Length in first line. + if i == 0: + out += lhs['length'] + + # Arrow between columns. + if i == 0: + out += u' -> ' + else: + out += u' ' # u' .. ' + + # Track number or alignment. + if i == 0: + out += lhs['track'] + ' ' + else: + out += ' ' * align_track + + # Line i of rhs track title. + if i in range(len(rhs_lines['col'])): + out += rhs_lines['col'][i] + + # Alignment up to the end of the right column. + if i in range(len(rhs_lines['raw'])): + align_title = len(rhs_lines['raw'][i]) + else: + align_title = 0 + align_used = align_track + align_title + if i == 0: + align_used += align_length_r + padding = col_width_r - align_used + out += ' ' * padding + + # Length in first line. + if i == 0: + out += rhs['length'] + + # Linebreak, except in the last line. + if i < max_line_count-1: + out += u'\n' + # Print complete line. + print_(out) + # Identify the album in question. # 'Match' header and similarity. print_('') @@ -297,65 +418,154 @@ def format_index(track_info): else: lhs = None if lhs: - lines.append((lhs, '', 0)) + lhs = { + 'track': u'', + 'title': lhs, + 'length': u'', + 'raw': { 'track': u'', 'title': lhs, 'length': u'' } + } + lines.append(('', lhs, '', 0, 0)) medium, disctitle = track_info.medium, track_info.disctitle + # Build all parts of both lhs and rhs, then compare line lengths and + # align. # Titles. new_title = track_info.title if not item.title.strip(): # If there's no title, we use the filename. cur_title = displayable_path(os.path.basename(item.path)) - lhs, rhs = cur_title, new_title + lhs_title, rhs_title = cur_title, new_title else: cur_title = item.title.strip() - lhs, rhs = ui.colordiff(cur_title, new_title) - lhs_width = len(cur_title) + lhs_title, rhs_title = ui.colordiff(cur_title, new_title) # Track number change. - cur_track, new_track = format_index(item), format_index(track_info) + templ = u'(#{0})' + cur_track = templ.format(format_index(item)) + new_track = templ.format(format_index(track_info)) if cur_track != new_track: if item.track in (track_info.index, track_info.medium_index): color = 'text_highlight_minor' else: color = 'text_highlight' - templ = ui.colorize(color, u' (#{0})') - lhs += templ.format(cur_track) - rhs += templ.format(new_track) - lhs_width += len(cur_track) + 4 + templ = ui.colorize(color, u'{0}') + else: + templ = u'{0}' + lhs_track = templ.format(cur_track) + rhs_track = templ.format(new_track) # Length change. if item.length and track_info.length and \ abs(item.length - track_info.length) > \ config['ui']['length_diff_thresh'].as_number(): - cur_length = ui.human_seconds_short(item.length) - new_length = ui.human_seconds_short(track_info.length) - templ = ui.colorize('text_highlight', u' ({0})') - lhs += templ.format(cur_length) - rhs += templ.format(new_length) - lhs_width += len(cur_length) + 3 + cur_length0 = ui.human_seconds_short(item.length) + new_length0 = ui.human_seconds_short(track_info.length) + cur_length = u'({})'.format(cur_length0) + new_length = u'({})'.format(new_length0) + lhs_length = ui.colorize('text_highlight', cur_length) + rhs_length = ui.colorize('text_highlight', new_length) + else: + cur_length = u'' + new_length = u'' + lhs_length = u'' + rhs_length = u'' # Penalties. penalties = penalty_string(match.distance.tracks[track_info]) - if penalties: - rhs += ' %s' % penalties - if lhs != rhs: - lines.append((' * %s' % lhs, rhs, lhs_width)) + # Construct comparison strings to check for differences + lhs_comp = ' '.join([cur_track, cur_title, cur_length]) + rhs_comp = ' '.join([new_track, new_title, new_length]) + # Construct lhs and rhs arrays + lhs = { + 'track': lhs_track, + 'title': lhs_title, + 'length': lhs_length, + 'raw' : { + 'track': cur_track, + 'title': cur_title, + 'length': cur_length, + } + } + rhs = { + 'track': rhs_track, + 'title': rhs_title, + 'length': rhs_length, + 'penalties': penalty_string(match.distance.tracks[track_info]), + 'raw' : { + 'track': new_track, + 'title': new_title, + 'length': new_length, + } + } + # Construct lhs and rhs line widths + lhs_width = len(lhs_comp) + rhs_width = len(rhs_comp) + + if lhs_comp != rhs_comp: + # Prefix changed tracks with U+2260: Not Equal To + prefix = ui.colorize('changed', ' \u2260 ') + lines.append((prefix, lhs, rhs, lhs_width, rhs_width)) elif config['import']['detail']: - lines.append((' * %s' % lhs, '', lhs_width)) + # Prefix unchanged tracks with * + prefix = ' * ' + lines.append((prefix, lhs, [], lhs_width, 0)) # Print each track in two columns, or across two lines. - col_width = (ui.term_width() - len(''.join([' * ', ' -> ']))) // 2 + joiner_width = len(''.join([' * ', ' -> '])) + indent_width = 4 + indent = ui.indent(indent_width) + col_width = (ui.term_width() - indent_width - joiner_width) // 2 if lines: - max_width = max(w for _, _, w in lines) - for lhs, rhs, lhs_width in lines: + # Size columns. + max_width_l = max(lw for _, _, _, lw, _ in lines) + max_width_r = max(rw for _, _, _, _, rw in lines) + + if (max_width_l <= col_width) and (max_width_r <= col_width): + col_width_l = max_width_l + col_width_r = max_width_r + elif ((max_width_l > col_width) or (max_width_r > col_width)) \ + and ((max_width_l + max_width_r) <= col_width * 2): + # Either left or right column larger than allowed, but the other is + # smaller than allowed - in total the content fits. + col_width_l = max_width_l + col_width_r = max_width_r + else: + col_width_l = col_width + col_width_r = col_width + + # Print lines. + for prefix, lhs, rhs, lhs_width, rhs_width in lines: + l_pre = indent + prefix + r_pre = indent + ui.indent(len(' * ')) if not rhs: - print_(lhs) - elif max_width > col_width: - print_(u'%s ->\n %s' % (lhs, rhs)) + pad_l = ' ' * (max_width_l - lhs_width) + lhs_str = "{0} {1} {2}{3}".format(lhs['track'], lhs['title'], + pad_l, lhs['length']) + print_(l_pre + lhs_str) + elif (lhs_width > col_width_l) or (rhs_width > col_width_r): + layout = \ + config['ui']['import']['albumdiff']['layout'].as_choice({ + 'column': 0, + 'newline': 1, + }) + if layout == 0: + # Word wrapping inside columns. + format_track_as_columns(indent, prefix, + col_width_l, col_width_r, lhs, rhs) + elif layout == 1: + # Wrap overlong track changes at column border. + format_track(indent, prefix, lhs_width, rhs_width, + max_width_l, max_width_r, lhs, rhs) else: - pad = max_width - lhs_width - print_(u'%s%s -> %s' % (lhs, ' ' * pad, rhs)) + pad_l = ' ' * (col_width_l - lhs_width) + pad_r = ' ' * (col_width_r - rhs_width) + template = "{0} {1} {2}{3}" + lhs_str = template.format( + lhs['track'], lhs['title'], pad_l, lhs['length']) + rhs_str = template.format( + rhs['track'], rhs['title'], pad_r, rhs['length']) + print_(l_pre + u'%s -> %s' % (lhs_str, rhs_str)) # Missing and unmatched tracks. if match.extra_tracks: From ced7dc48ee29a6dd0991f0baa884c4ef3ec25030 Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Sun, 1 Nov 2015 22:29:47 +0100 Subject: [PATCH 13/45] UI: Add config options to set match indentation --- beets/config_default.yaml | 4 ++++ beets/ui/commands.py | 49 ++++++++++++++++++++++++--------------- 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/beets/config_default.yaml b/beets/config_default.yaml index 51ab224598..96fc26c2fc 100644 --- a/beets/config_default.yaml +++ b/beets/config_default.yaml @@ -74,6 +74,10 @@ ui: text_diff_changed: ['bold', 'red'] action_description: ['blue'] import: + indentation: + match_header: 4 + match_details: 8 + match_tracklist: 6 albumdiff: layout: newline diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 5165a75d91..6c7fcd50b3 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -238,7 +238,7 @@ def format_track(indent, prefix, lhs_width, rhs_width, col_width_l, col_width_r, lhs['track'], lhs['title'], pad_l, lhs['length']) rhs_str = template.format( rhs['track'], rhs['title'], pad_r, rhs['length']) - print_(u'{0}{1} ->\n{2}{3}'.format(indent + prefix, lhs_str, indent + ui.indent(len(' * ')), rhs_str)) + print_(u'{0}{1} ->\n{2}{3}'.format(indent + prefix, lhs_str, indent + ui.indent(len('* ')), rhs_str)) def format_track_as_columns(indent, prefix, col_width_l, col_width_r, lhs, rhs): @@ -290,7 +290,7 @@ def format_track_as_columns(indent, prefix, col_width_l, col_width_r, out += prefix out += lhs['track'] + ' ' else: - out += ui.indent(len(' * ')) + out += ui.indent(len('* ')) out += ' ' * align_track # Line i of lhs track title. @@ -349,29 +349,37 @@ def format_track_as_columns(indent, prefix, col_width_l, col_width_r, # Print complete line. print_(out) - # Identify the album in question. + # Identify the album in question (Match Header). + match_header_indent_width = \ + config['ui']['import']['indentation']['match_header'].as_number() + header_indent = ui.indent(match_header_indent_width) # 'Match' header and similarity. print_('') - print_(ui.indent(2) + u'Match: (%s):' % dist_string(match.distance)) + print_(header_indent + u'Match: (%s):' % dist_string(match.distance)) # Artist name and album title. artist_album_str = u"{0.artist} - {0.album}".format(match.info) - print_(ui.indent(2) + dist_colorize(artist_album_str, match.distance)) + print_(header_indent + dist_colorize(artist_album_str, match.distance)) # Penalties. penalties = penalty_string(match.distance) if penalties: - print_(ui.indent(2) + penalties) + print_(header_indent + penalties) # Disambiguation disambig = disambig_string(match.info) if disambig: - print_(ui.indent(2) + ui.colorize('text_highlight_minor', disambig)) + print_(header_indent + ui.colorize('text_highlight_minor', disambig)) # Data URL. if match.info.data_url: url = ui.colorize('text_highlight_minor', '%s' % match.info.data_url) - print_(ui.indent(2) + url) + print_(header_indent + url) + + # Match details. + match_detail_indent_width = \ + config['ui']['import']['indentation']['match_details'].as_number() + detail_indent = ui.indent(match_detail_indent_width) # Artist. artist_l, artist_r = cur_artist or '', match.info.artist @@ -381,18 +389,20 @@ def format_track_as_columns(indent, prefix, col_width_l, col_width_r, if artist_l != artist_r: artist_l, artist_r = ui.colordiff(artist_l, artist_r) # Prefix with U+2260: Not Equal To - print_(ui.indent(2) + ui.colorize('changed', u'\u2260'), u'Artist:', artist_l, u'->', artist_r) + print_(detail_indent + ui.colorize('changed', u'\u2260'), + u'Artist:', artist_l, u'->', artist_r) else: - print_(ui.indent(2) + '=', 'Artist:', artist_r) + print_(detail_indent + '=', 'Artist:', artist_r) # Album album_l, album_r = cur_album or '', match.info.album if (cur_album != match.info.album and match.info.album != VARIOUS_ARTISTS): album_l, album_r = ui.colordiff(album_l, album_r) # Prefix with U+2260: Not Equal To - print_(ui.indent(2) + ui.colorize('changed', u'\u2260'), u'Album:', album_l, u'->', album_r) + print_(detail_indent + ui.colorize('changed', u'\u2260'), + u'Album:', album_l, u'->', album_r) else: - print_(ui.indent(2) + '=', 'Album:', album_r) + print_(detail_indent + '=', 'Album:', album_r) # Tracks. pairs = match.mapping.items() @@ -504,18 +514,19 @@ def format_track_as_columns(indent, prefix, col_width_l, col_width_r, if lhs_comp != rhs_comp: # Prefix changed tracks with U+2260: Not Equal To - prefix = ui.colorize('changed', ' \u2260 ') + prefix = ui.colorize('changed', '\u2260 ') lines.append((prefix, lhs, rhs, lhs_width, rhs_width)) elif config['import']['detail']: # Prefix unchanged tracks with * - prefix = ' * ' + prefix = '* ' lines.append((prefix, lhs, [], lhs_width, 0)) # Print each track in two columns, or across two lines. - joiner_width = len(''.join([' * ', ' -> '])) - indent_width = 4 - indent = ui.indent(indent_width) - col_width = (ui.term_width() - indent_width - joiner_width) // 2 + joiner_width = len(''.join(['* ', ' -> '])) + tracklist_indent_width = \ + config['ui']['import']['indentation']['match_tracklist'].as_number() + indent = ui.indent(tracklist_indent_width) + col_width = (ui.term_width() - tracklist_indent_width - joiner_width) // 2 if lines: # Size columns. max_width_l = max(lw for _, _, _, lw, _ in lines) @@ -537,7 +548,7 @@ def format_track_as_columns(indent, prefix, col_width_l, col_width_r, # Print lines. for prefix, lhs, rhs, lhs_width, rhs_width in lines: l_pre = indent + prefix - r_pre = indent + ui.indent(len(' * ')) + r_pre = indent + ui.indent(len('* ')) if not rhs: pad_l = ' ' * (max_width_l - lhs_width) lhs_str = "{0} {1} {2}{3}".format(lhs['track'], lhs['title'], From 3356afb2c5845ad2ec37bb90de4468322cb7f885 Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Sun, 1 Nov 2015 23:08:37 +0100 Subject: [PATCH 14/45] UI: Always show disk info in tracklist --- beets/ui/commands.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 6c7fcd50b3..ae5355d223 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -392,7 +392,7 @@ def format_track_as_columns(indent, prefix, col_width_l, col_width_r, print_(detail_indent + ui.colorize('changed', u'\u2260'), u'Artist:', artist_l, u'->', artist_r) else: - print_(detail_indent + '=', 'Artist:', artist_r) + print_(detail_indent + '*', 'Artist:', artist_r) # Album album_l, album_r = cur_album or '', match.info.album @@ -402,7 +402,7 @@ def format_track_as_columns(indent, prefix, col_width_l, col_width_r, print_(detail_indent + ui.colorize('changed', u'\u2260'), u'Album:', album_l, u'->', album_r) else: - print_(detail_indent + '=', 'Album:', album_r) + print_(detail_indent + '*', 'Album:', album_r) # Tracks. pairs = match.mapping.items() @@ -419,20 +419,20 @@ def format_track_as_columns(indent, prefix, col_width_l, col_width_r, if medium != track_info.medium or disctitle != track_info.disctitle: media = match.info.media or 'Media' if match.info.mediums > 1 and track_info.disctitle: - lhs = '%s %s: %s' % (media, track_info.medium, + out = '* %s %s: %s' % (media, track_info.medium, track_info.disctitle) - elif match.info.mediums > 1: - lhs = '%s %s' % (media, track_info.medium) elif track_info.disctitle: - lhs = '%s: %s' % (media, track_info.disctitle) + out = '* %s: %s' % (media, track_info.disctitle) else: - lhs = None - if lhs: + out = '* %s %s' % (media, track_info.medium) + if out: + lhs = { - 'track': u'', - 'title': lhs, - 'length': u'', - 'raw': { 'track': u'', 'title': lhs, 'length': u'' } + 'disk': detail_indent + out, + 'track': None, + 'title': None, + 'length': None, + 'raw': None } lines.append(('', lhs, '', 0, 0)) medium, disctitle = track_info.medium, track_info.disctitle @@ -488,6 +488,7 @@ def format_track_as_columns(indent, prefix, col_width_l, col_width_r, rhs_comp = ' '.join([new_track, new_title, new_length]) # Construct lhs and rhs arrays lhs = { + 'disk': None, 'track': lhs_track, 'title': lhs_title, 'length': lhs_length, @@ -550,10 +551,13 @@ def format_track_as_columns(indent, prefix, col_width_l, col_width_r, l_pre = indent + prefix r_pre = indent + ui.indent(len('* ')) if not rhs: - pad_l = ' ' * (max_width_l - lhs_width) - lhs_str = "{0} {1} {2}{3}".format(lhs['track'], lhs['title'], - pad_l, lhs['length']) - print_(l_pre + lhs_str) + if lhs['disk']: + print_(lhs['disk']) + else: + pad_l = ' ' * (max_width_l - lhs_width) + lhs_str = "{0} {1} {2}{3}".format( + lhs['track'], lhs['title'], pad_l, lhs['length']) + print_(l_pre + lhs_str) elif (lhs_width > col_width_l) or (rhs_width > col_width_r): layout = \ config['ui']['import']['albumdiff']['layout'].as_choice({ From 6a006ede8fa5129e84bde211709043ebddeca4d6 Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Sun, 1 Nov 2015 23:10:24 +0100 Subject: [PATCH 15/45] UI: change default configs for import layout --- beets/config_default.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/beets/config_default.yaml b/beets/config_default.yaml index 96fc26c2fc..6523c72ef4 100644 --- a/beets/config_default.yaml +++ b/beets/config_default.yaml @@ -75,11 +75,11 @@ ui: action_description: ['blue'] import: indentation: - match_header: 4 - match_details: 8 - match_tracklist: 6 + match_header: 2 + match_details: 2 + match_tracklist: 5 albumdiff: - layout: newline + layout: column format_item: $artist - $album - $title format_album: $albumartist - $album From bd34709499a7702698d0d0256d75f2da13ce875b Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Tue, 17 Nov 2015 20:41:05 +0100 Subject: [PATCH 16/45] UI: add `text`, `text_faint` colors --- beets/config_default.yaml | 2 ++ beets/ui/__init__.py | 1 + 2 files changed, 3 insertions(+) diff --git a/beets/config_default.yaml b/beets/config_default.yaml index 6523c72ef4..41c089b618 100644 --- a/beets/config_default.yaml +++ b/beets/config_default.yaml @@ -61,6 +61,8 @@ ui: action_default: ['bold', 'cyan'] action: ['bold', 'blue'] # New Colors + text: ['normal'] + text_faint: ['faint'] import_path: ['bold', 'inverse', 'blue'] import_path_items: ['bold', 'blue'] added: ['green'] diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index bced051c98..3914e91d71 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -461,6 +461,7 @@ def human_seconds_short(interval): COLOR_NAMES = ['text_success', 'text_warning', 'text_error', 'text_highlight', 'text_highlight_minor', 'action_default', 'action', # New Colors + 'text', 'text_faint', 'import_path', 'import_path_items', 'action_description', 'added', 'removed', 'changed', From 3ce06380775a09c0c36a088a4928cccb6e4cf669 Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Tue, 17 Nov 2015 20:41:54 +0100 Subject: [PATCH 17/45] UI: fixup typo in comment, remove debug statement --- beets/ui/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 3914e91d71..11c6e4d89f 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -651,11 +651,10 @@ def split_into_lines(string, raw_string, first_width, middle_width, last_width): length not longer than first_width, the last substring has a length not longer than last_width, and all other substrings have a length not longer than middle_width. - if raw_string is defined, raw_string and string contain the same words, but + If raw_string is defined, raw_string and string contain the same words, but string contains ANSI codes at word borders. Use raw_string to find substrings, but return the words of string. """ - #print_('str: {}\nraw: {}\nfw: {}\nmw: {}\nlw: {}'.format(string, raw_string, first_width, middle_width, last_width)) words_raw = raw_string.split() words = string.split() assert len(words_raw) == len(words) From 8f44173fd6d245067df2c5c132fe6265d6c91c3b Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Tue, 17 Nov 2015 20:43:50 +0100 Subject: [PATCH 18/45] UI: cleanup: colorize functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - reintroduce support for legacy colors - replace DARK_COLORS and LIGHT_COLORS with LEGACY_COLORS and map color names onto their counterpart in the new “advanced” color definition system - also continue to support new “advanced” colors - cleaner code (at least I think so) --- beets/ui/__init__.py | 99 ++++++++++++++++++++++++-------------------- 1 file changed, 54 insertions(+), 45 deletions(-) diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 11c6e4d89f..5d310b803f 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -39,6 +39,7 @@ from beets.util.functemplate import Template from beets import config from beets.util import confit +from beets.util.confit import ConfigTypeError from beets.autotag import mb from beets.dbcore import query as db_query @@ -397,30 +398,28 @@ def human_seconds_short(interval): # http://dev.pocoo.org/hg/pygments-main/file/b2deea5b5030/pygments/console.py # (pygments is by Tim Hatch, Armin Ronacher, et al.) COLOR_ESCAPE = "\x1b[" -DARK_COLORS = { - "black": 0, - "darkred": 1, - "darkgreen": 2, - "brown": 3, - "darkyellow": 3, - "darkblue": 4, - "purple": 5, - "darkmagenta": 5, - "teal": 6, - "darkcyan": 6, - "lightgray": 7 -} -LIGHT_COLORS = { - "darkgray": 0, - "red": 1, - "green": 2, - "yellow": 3, - "blue": 4, - "fuchsia": 5, - "magenta": 5, - "turquoise": 6, - "cyan": 6, - "white": 7 +LEGACY_COLORS = { + "black": ['black'], + "darkred": ['red'], + "darkgreen": ['green'], + "brown": ['yellow'], + "darkyellow": ['yellow'], + "darkblue": ['blue'], + "purple": ['magenta'], + "darkmagenta": ['magenta'], + "teal": ['cyan'], + "darkcyan": ['cyan'], + "lightgray": ['white'], + "darkgray": ['bold', 'black'], + "red": ['bold', 'red'], + "green": ['bold', 'green'], + "yellow": ['bold', 'yellow'], + "blue": ['bold', 'blue'], + "fuchsia": ['bold', 'magenta'], + "magenta": ['bold', 'magenta'], + "turquoise": ['bold', 'cyan'], + "cyan": ['bold', 'cyan'], + "white": ['bold', 'white'] } # All ANSI Colors. ANSI_CODES = { @@ -472,24 +471,17 @@ def human_seconds_short(interval): def _colorize(color, text): """Returns a string that prints the given text in the given color - in a terminal that is ANSI color-aware. The color must be something - in DARK_COLORS or LIGHT_COLORS. + in a terminal that is ANSI color-aware. The color must be a list of strings + out of ANSI_CODES. """ - if not isinstance(color, basestring): - # Non-strings are lists with advanced color definitions - escape = "" - for color_def in color: - - color_def = "{0}".format(color_def) # TODO what the fuck - - if color_def in ANSI_CODES.keys(): - escape = escape + COLOR_ESCAPE + "%im" % ANSI_CODES[color_def] - #elif color_def in DARK_COLORS: - # escape = COLOR_ESCAPE + "%im" % (DARK_COLORS[color_def] + 30) - #elif color_def in LIGHT_COLORS: - # escape = COLOR_ESCAPE + "%i;01m" % (LIGHT_COLORS[color_def] + 30) - else: - raise ValueError('no such color %s', color) + # Construct escape sequence to be put before the text by iterating + # over all "ANSI codes" in `color`. + escape = "" + for code in color: + if code in ANSI_CODES.keys(): + escape = escape + COLOR_ESCAPE + "%im" % ANSI_CODES[code] + else: + raise ValueError('no such ANSI code %s', code) return escape + text + RESET_COLOR @@ -500,10 +492,27 @@ def colorize(color_name, text): if config['ui']['color']: global COLORS if not COLORS: - # TODO uncomment and repair - #COLORS = dict((name, config['ui']['colors'][name].get(unicode)) - COLORS = dict((name, config['ui']['colors'][name]) - for name in COLOR_NAMES) + # Read all color configurations and set global variable COLORS. + COLORS = dict() + for name in COLOR_NAMES: + # Convert legacy color definitions (strings) into the new + # list-based color definitions. Do this by trying to read the + # color definition from the configuration as unicode - if this + # is successful, the color definition is a legacy definition + # and has to be converted. + try: + color_def = config['ui']['colors'][name].get(unicode) + except ConfigTypeError: + # Normal color definition (type: list of unicode). + color_def = config['ui']['colors'][name].get(list) + else: + # Legacy color definition (type: unicode). Convert. + if color_def in LEGACY_COLORS: + color_def = LEGACY_COLORS[color_def] + else: + raise ValueError('no such color %s', color) + + COLORS[name] = color_def # In case a 3rd party plugin is still passing the actual color ('red') # instead of the abstract color name ('text_error') color = COLORS.get(color_name) From c593327b2a983c71f3810e7db6a44ed0d8afec26 Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Tue, 17 Nov 2015 21:22:15 +0100 Subject: [PATCH 19/45] UI: refactor code for change of track # or length --- beets/ui/commands.py | 51 +++++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index ae5355d223..fe33afc8a4 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -450,35 +450,48 @@ def format_track_as_columns(indent, prefix, col_width_l, col_width_r, lhs_title, rhs_title = ui.colordiff(cur_title, new_title) # Track number change. - templ = u'(#{0})' - cur_track = templ.format(format_index(item)) - new_track = templ.format(format_index(track_info)) + cur_track = format_index(item) + new_track = format_index(track_info) if cur_track != new_track: if item.track in (track_info.index, track_info.medium_index): - color = 'text_highlight_minor' + cur_track_templ = u'(#{})' + new_track_templ = u'(#{})' + cur_track_color = 'text_highlight_minor' + new_track_color = 'text_highlight_minor' else: - color = 'text_highlight' - templ = ui.colorize(color, u'{0}') + cur_track_templ = u'(#{})' + new_track_templ = u'(#{})' + cur_track_color = 'text_highlight' + new_track_color = 'text_highlight' else: - templ = u'{0}' - lhs_track = templ.format(cur_track) - rhs_track = templ.format(new_track) + cur_track_templ = u'' + new_track_templ = u'' + cur_track_color = 'text_faint' + new_track_color = 'text_faint' + cur_track = cur_track_templ.format(cur_track) + new_track = new_track_templ.format(new_track) + lhs_track = ui.colorize(cur_track_color, cur_track) + rhs_track = ui.colorize(new_track_color, new_track) # Length change. if item.length and track_info.length and \ abs(item.length - track_info.length) > \ config['ui']['length_diff_thresh'].as_number(): - cur_length0 = ui.human_seconds_short(item.length) - new_length0 = ui.human_seconds_short(track_info.length) - cur_length = u'({})'.format(cur_length0) - new_length = u'({})'.format(new_length0) - lhs_length = ui.colorize('text_highlight', cur_length) - rhs_length = ui.colorize('text_highlight', new_length) + cur_length_templ = u'({})' + new_length_templ = u'({})' + cur_length_color = 'text_highlight' + new_length_color = 'text_highlight' else: - cur_length = u'' - new_length = u'' - lhs_length = u'' - rhs_length = u'' + cur_length_templ = u'({})' + new_length_templ = u'({})' + cur_length_color = 'text_highlight_minor' + new_length_color = 'text_highlight_minor' + cur_length0 = ui.human_seconds_short(item.length) + new_length0 = ui.human_seconds_short(track_info.length) + cur_length = cur_length_templ.format(cur_length0) + new_length = new_length_templ.format(new_length0) + lhs_length = ui.colorize(cur_length_color, cur_length) + rhs_length = ui.colorize(new_length_color, new_length) # Penalties. penalties = penalty_string(match.distance.tracks[track_info]) From 9a4a15f815a38578801da597dd5dbccc911053c2 Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Tue, 17 Nov 2015 21:24:07 +0100 Subject: [PATCH 20/45] UI: fixup track # alignment in column view --- beets/ui/commands.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index fe33afc8a4..1b648b01c4 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -277,7 +277,6 @@ def format_track_as_columns(indent, prefix, col_width_l, col_width_r, # Construct string for all lines of both columns. max_line_count = max(len(lhs_lines['col']), len(rhs_lines['col'])) - align_track = len(lhs['raw']['track']) + 1 align_length_l = len(lhs['raw']['length']) align_length_r = len(rhs['raw']['length']) out = u'' @@ -285,13 +284,17 @@ def format_track_as_columns(indent, prefix, col_width_l, col_width_r, # Indentation out += indent - # Prefix and track number, or alignment. + # Prefix. if i == 0: out += prefix - out += lhs['track'] + ' ' else: out += ui.indent(len('* ')) - out += ' ' * align_track + + # Track number or alignment + if i == 0 and lhs_track_len > 0: + out += lhs['track'] + ' ' + else: + out += ' ' * lhs_track_len # Line i of lhs track title. if i in range(len(lhs_lines['col'])): @@ -302,7 +305,7 @@ def format_track_as_columns(indent, prefix, col_width_l, col_width_r, align_title = len(lhs_lines['raw'][i]) else: align_title = 0 - align_used = align_track + align_title + align_used = lhs_track_len + align_title if i == 0: align_used += align_length_l padding = col_width_l - align_used @@ -319,10 +322,10 @@ def format_track_as_columns(indent, prefix, col_width_l, col_width_r, out += u' ' # u' .. ' # Track number or alignment. - if i == 0: - out += lhs['track'] + ' ' + if i == 0 and rhs_track_len > 0: + out += rhs['track'] + ' ' else: - out += ' ' * align_track + out += ' ' * rhs_track_len # Line i of rhs track title. if i in range(len(rhs_lines['col'])): @@ -333,7 +336,7 @@ def format_track_as_columns(indent, prefix, col_width_l, col_width_r, align_title = len(rhs_lines['raw'][i]) else: align_title = 0 - align_used = align_track + align_title + align_used = rhs_track_len + align_title if i == 0: align_used += align_length_r padding = col_width_r - align_used From 69e2f3afd1988cb397f6740ca508787ba681ec08 Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Tue, 17 Nov 2015 21:24:38 +0100 Subject: [PATCH 21/45] UI: refactor calculation of available columns --- beets/ui/commands.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 1b648b01c4..ee984f732f 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -245,28 +245,26 @@ def format_track_as_columns(indent, prefix, col_width_l, col_width_r, """docstring for format_track_as_columns""" # Calculate available space for word wrapping. # Left-hand side. - lhs_track_len = len(lhs['raw']['track']) + lhs_track_len = len(lhs['raw']['track']) lhs_length_len = len(lhs['raw']['length']) - lhs_spaces_first = 2 - lhs_used_first = lhs_track_len + lhs_spaces_first + lhs_length_len + if lhs_track_len > 0: lhs_track_len += 1 # Space. + if lhs_length_len > 0: lhs_length_len += 1 # Space. + lhs_used_first = lhs_track_len + lhs_length_len + lhs_used_middle = lhs_track_len + lhs_used_last = lhs_track_len col_width_l_first = col_width_l - lhs_used_first - lhs_spaces_middle = 1 - lhs_used_middle = lhs_track_len + lhs_spaces_middle col_width_l_middle = col_width_l - lhs_used_middle - lhs_spaces_last = 1 - lhs_used_last = lhs_track_len + lhs_spaces_last col_width_l_last = col_width_l - lhs_used_last # Right-hand side. - rhs_track_len = len(rhs['raw']['track']) + rhs_track_len = len(rhs['raw']['track']) rhs_length_len = len(rhs['raw']['length']) - rhs_spaces_first = 2 - rhs_used_first = rhs_track_len + rhs_spaces_first + rhs_length_len + if rhs_track_len > 0: rhs_track_len += 1 # Space. + if rhs_length_len > 0: rhs_length_len += 1 # Space. + rhs_used_first = rhs_track_len + rhs_length_len + rhs_used_middle = rhs_track_len + rhs_used_last = rhs_track_len col_width_r_first = col_width_r - rhs_used_first - rhs_spaces_middle = 1 - rhs_used_middle = rhs_track_len + rhs_spaces_middle col_width_r_middle = col_width_r - rhs_used_middle - rhs_spaces_last = 1 - rhs_used_last = rhs_track_len + rhs_spaces_last col_width_r_last = col_width_r - rhs_used_last # Calculate word wrapping. From 0bb39799a480c493da073f3e12103cece57421d5 Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Sat, 23 Jan 2016 13:04:50 +0100 Subject: [PATCH 22/45] refactor printing the match header --- beets/ui/commands.py | 54 +++++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index ee984f732f..770493e52b 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -350,32 +350,40 @@ def format_track_as_columns(indent, prefix, col_width_l, col_width_r, # Print complete line. print_(out) - # Identify the album in question (Match Header). - match_header_indent_width = \ - config['ui']['import']['indentation']['match_header'].as_number() - header_indent = ui.indent(match_header_indent_width) - # 'Match' header and similarity. - print_('') - print_(header_indent + u'Match: (%s):' % dist_string(match.distance)) - - # Artist name and album title. - artist_album_str = u"{0.artist} - {0.album}".format(match.info) - print_(header_indent + dist_colorize(artist_album_str, match.distance)) + def show_match_header(match): + """Print out a “header” identifying the suggested match (album name, artist name,…) and summarizing the changes that would be made should the user accept the match.""" + # Read match header indentation width from config. + match_header_indent_width = \ + config['ui']['import']['indentation']['match_header'].as_number() + header_indent = ui.indent(match_header_indent_width) - # Penalties. - penalties = penalty_string(match.distance) - if penalties: - print_(header_indent + penalties) + # Print newline at beginning of change block. + print_(u'') - # Disambiguation - disambig = disambig_string(match.info) - if disambig: - print_(header_indent + ui.colorize('text_highlight_minor', disambig)) + # 'Match' line and similarity. + print_(header_indent + u'Match (%s):' % dist_string(match.distance)) - # Data URL. - if match.info.data_url: - url = ui.colorize('text_highlight_minor', '%s' % match.info.data_url) - print_(header_indent + url) + # Artist name and album title. + artist_album_str = u'{0.artist} - {0.album}'.format(match.info) + print_(header_indent + dist_colorize(artist_album_str, match.distance)) + + # Penalties. + penalties = penalty_string(match.distance) + if penalties: + print_(header_indent + penalties) + + # Disambiguation. + disambig = disambig_string(match.info) + if disambig: + print_(header_indent + ui.colorize('text_highlight_minor', disambig)) + + # Data URL. + if match.info.data_url: + url = ui.colorize('text_highlight_minor', '%s' % match.info.data_url) + print_(header_indent + url) + + # Print the match header. + show_match_header(match) # Match details. match_detail_indent_width = \ From 7c8ac73e5f3fe008d6bd5602e1f170575468bd30 Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Sat, 23 Jan 2016 17:17:21 +0100 Subject: [PATCH 23/45] change string to unicode; add whitespace --- beets/ui/commands.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 770493e52b..d460466ffe 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -231,8 +231,8 @@ def format_index(track_info): def format_track(indent, prefix, lhs_width, rhs_width, col_width_l, col_width_r, lhs, rhs): """docstring for format_track""" # Print track - pad_l = ' ' * (col_width_l - lhs_width) - pad_r = ' ' * (col_width_r - rhs_width) + pad_l = u' ' * (col_width_l - lhs_width) + pad_r = u' ' * (col_width_r - rhs_width) template = "{0} {1} {2}{3}" lhs_str = template.format( lhs['track'], lhs['title'], pad_l, lhs['length']) @@ -255,6 +255,7 @@ def format_track_as_columns(indent, prefix, col_width_l, col_width_r, col_width_l_first = col_width_l - lhs_used_first col_width_l_middle = col_width_l - lhs_used_middle col_width_l_last = col_width_l - lhs_used_last + # Right-hand side. rhs_track_len = len(rhs['raw']['track']) rhs_length_len = len(rhs['raw']['length']) From 605b73181cedb95da97cb69773b9e31eee7bd16b Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Sat, 23 Jan 2016 17:19:57 +0100 Subject: [PATCH 24/45] split show_change method into parts --- beets/ui/commands.py | 486 +++++++++++++++++++++++-------------------- 1 file changed, 265 insertions(+), 221 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index d460466ffe..f8361ed846 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -351,8 +351,11 @@ def format_track_as_columns(indent, prefix, col_width_l, col_width_r, # Print complete line. print_(out) - def show_match_header(match): - """Print out a “header” identifying the suggested match (album name, artist name,…) and summarizing the changes that would be made should the user accept the match.""" + def show_match_header(): + """Print out a 'header' identifying the suggested match (album name, + artist name,...) and summarizing the changes that would be made should + the user accept the match. + """ # Read match header indentation width from config. match_header_indent_width = \ config['ui']['import']['indentation']['match_header'].as_number() @@ -383,51 +386,52 @@ def show_match_header(match): url = ui.colorize('text_highlight_minor', '%s' % match.info.data_url) print_(header_indent + url) - # Print the match header. - show_match_header(match) - - # Match details. - match_detail_indent_width = \ - config['ui']['import']['indentation']['match_details'].as_number() - detail_indent = ui.indent(match_detail_indent_width) - - # Artist. - artist_l, artist_r = cur_artist or '', match.info.artist - if artist_r == VARIOUS_ARTISTS: - # Hide artists for VA releases. - artist_l, artist_r = u'', u'' - if artist_l != artist_r: - artist_l, artist_r = ui.colordiff(artist_l, artist_r) - # Prefix with U+2260: Not Equal To - print_(detail_indent + ui.colorize('changed', u'\u2260'), - u'Artist:', artist_l, u'->', artist_r) - else: - print_(detail_indent + '*', 'Artist:', artist_r) - - # Album - album_l, album_r = cur_album or '', match.info.album - if (cur_album != match.info.album and match.info.album != VARIOUS_ARTISTS): - album_l, album_r = ui.colordiff(album_l, album_r) - # Prefix with U+2260: Not Equal To - print_(detail_indent + ui.colorize('changed', u'\u2260'), - u'Album:', album_l, u'->', album_r) - else: - print_(detail_indent + '*', 'Album:', album_r) - - # Tracks. - pairs = match.mapping.items() - pairs.sort(key=lambda (_, track_info): track_info.index) + def get_match_details_indentation(): + """Reads match detail indentation width from config. + """ + match_detail_indent_width = \ + config['ui']['import']['indentation']['match_details'].as_number() + return ui.indent(match_detail_indent_width) - # Build up LHS and RHS for track difference display. The `lines` list - # contains ``(lhs, rhs, width)`` tuples where `width` is the length (in - # characters) of the uncolorized LHS. - lines = [] - medium = disctitle = None - for item, track_info in pairs: + def show_match_details(): + """Print out the details of the match, including changes in album name + and artist name. + """ + # Read match detail indentation width from config. + detail_indent = get_match_details_indentation() + + # Artist. + artist_l, artist_r = cur_artist or '', match.info.artist + if artist_r == VARIOUS_ARTISTS: + # Hide artists for VA releases. + artist_l, artist_r = u'', u'' + if artist_l != artist_r: + artist_l, artist_r = ui.colordiff(artist_l, artist_r) + # Prefix with U+2260: Not Equal To + print_(detail_indent + ui.colorize('changed', u'\u2260'), + u'Artist:', artist_l, u'->', artist_r) + else: + print_(detail_indent + '*', 'Artist:', artist_r) + + # Album + album_l, album_r = cur_album or '', match.info.album + if (cur_album != match.info.album \ + and match.info.album != VARIOUS_ARTISTS): + album_l, album_r = ui.colordiff(album_l, album_r) + # Prefix with U+2260: Not Equal To + print_(detail_indent + ui.colorize('changed', u'\u2260'), + u'Album:', album_l, u'->', album_r) + else: + print_(detail_indent + '*', 'Album:', album_r) - # Medium number and title. - if medium != track_info.medium or disctitle != track_info.disctitle: + def show_match_tracks(): + """Print out the tracks of the match, summarizing changes the match + suggests for them. + """ + def make_medium_info_line(): + """Construct a line with the current medium’s info.""" media = match.info.media or 'Media' + # Build output string. if match.info.mediums > 1 and track_info.disctitle: out = '* %s %s: %s' % (media, track_info.medium, track_info.disctitle) @@ -435,8 +439,93 @@ def show_match_header(match): out = '* %s: %s' % (media, track_info.disctitle) else: out = '* %s %s' % (media, track_info.medium) - if out: - + return out + + def make_track_titles(item, track_info): + """docstring for fname + """ + new_title = track_info.title + if not item.title.strip(): + # If there's no title, we use the filename. Don’t colordiff. + cur_title = displayable_path(os.path.basename(item.path)) + return cur_title, new_title + else: + # If there is a title, highlight differences. + cur_title = item.title.strip() + return ui.colordiff(cur_title, new_title) + + def make_track_numbers(item, track_info): + """docstring for fname + """ + cur_track = format_index(item) + new_track = format_index(track_info) + if cur_track != new_track: + if item.track in (track_info.index, track_info.medium_index): + cur_track_templ = u'(#{})' + new_track_templ = u'(#{})' + cur_track_color = 'text_highlight_minor' + new_track_color = 'text_highlight_minor' + else: + cur_track_templ = u'(#{})' + new_track_templ = u'(#{})' + cur_track_color = 'text_highlight' + new_track_color = 'text_highlight' + else: + cur_track_templ = u'' + new_track_templ = u'' + cur_track_color = 'text_faint' + new_track_color = 'text_faint' + cur_track = cur_track_templ.format(cur_track) + new_track = new_track_templ.format(new_track) + lhs_track = ui.colorize(cur_track_color, cur_track) + rhs_track = ui.colorize(new_track_color, new_track) + return lhs_track, rhs_track + + def make_track_lengths(item, track_info): + """ + """ + if item.length and track_info.length and \ + abs(item.length - track_info.length) > \ + config['ui']['length_diff_thresh'].as_number(): + cur_length_templ = u'({})' + new_length_templ = u'({})' + cur_length_color = 'text_highlight' + new_length_color = 'text_highlight' + else: + cur_length_templ = u'({})' + new_length_templ = u'({})' + cur_length_color = 'text_highlight_minor' + new_length_color = 'text_highlight_minor' + cur_length0 = ui.human_seconds_short(item.length) + new_length0 = ui.human_seconds_short(track_info.length) + cur_length = cur_length_templ.format(cur_length0) + new_length = new_length_templ.format(new_length0) + lhs_length = ui.colorize(cur_length_color, cur_length) + rhs_length = ui.colorize(new_length_color, new_length) + return lhs_length, rhs_length + + # Read match detail indentation width from config. + detail_indent = get_match_details_indentation() + + # Tracks. + pairs = match.mapping.items() + pairs.sort(key=lambda (_, track_info): track_info.index) + + ### ----------------------------------------------------------------- + ### Build lines array + ### ----------------------------------------------------------------- + + # Build up LHS and RHS for track difference display. The `lines` list + # contains ``prefix, lhs, rhs, lhs_width, rhs_width`` tuples where + # width is the length (in characters) of the uncolorized LHS. + lines = [] + medium = disctitle = None + for item, track_info in pairs: + + # If the track is the first on a new medium, show medium + # number and title. + if medium != track_info.medium or disctitle != track_info.disctitle: + out = make_medium_info_line() lhs = { 'disk': detail_indent + out, 'track': None, @@ -445,185 +534,140 @@ def show_match_header(match): 'raw': None } lines.append(('', lhs, '', 0, 0)) - medium, disctitle = track_info.medium, track_info.disctitle - - # Build all parts of both lhs and rhs, then compare line lengths and - # align. - # Titles. - new_title = track_info.title - if not item.title.strip(): - # If there's no title, we use the filename. - cur_title = displayable_path(os.path.basename(item.path)) - lhs_title, rhs_title = cur_title, new_title - else: - cur_title = item.title.strip() - lhs_title, rhs_title = ui.colordiff(cur_title, new_title) - - # Track number change. - cur_track = format_index(item) - new_track = format_index(track_info) - if cur_track != new_track: - if item.track in (track_info.index, track_info.medium_index): - cur_track_templ = u'(#{})' - new_track_templ = u'(#{})' - cur_track_color = 'text_highlight_minor' - new_track_color = 'text_highlight_minor' - else: - cur_track_templ = u'(#{})' - new_track_templ = u'(#{})' - cur_track_color = 'text_highlight' - new_track_color = 'text_highlight' - else: - cur_track_templ = u'' - new_track_templ = u'' - cur_track_color = 'text_faint' - new_track_color = 'text_faint' - cur_track = cur_track_templ.format(cur_track) - new_track = new_track_templ.format(new_track) - lhs_track = ui.colorize(cur_track_color, cur_track) - rhs_track = ui.colorize(new_track_color, new_track) - - # Length change. - if item.length and track_info.length and \ - abs(item.length - track_info.length) > \ - config['ui']['length_diff_thresh'].as_number(): - cur_length_templ = u'({})' - new_length_templ = u'({})' - cur_length_color = 'text_highlight' - new_length_color = 'text_highlight' - else: - cur_length_templ = u'({})' - new_length_templ = u'({})' - cur_length_color = 'text_highlight_minor' - new_length_color = 'text_highlight_minor' - cur_length0 = ui.human_seconds_short(item.length) - new_length0 = ui.human_seconds_short(track_info.length) - cur_length = cur_length_templ.format(cur_length0) - new_length = new_length_templ.format(new_length0) - lhs_length = ui.colorize(cur_length_color, cur_length) - rhs_length = ui.colorize(new_length_color, new_length) - - # Penalties. - penalties = penalty_string(match.distance.tracks[track_info]) - - # Construct comparison strings to check for differences - lhs_comp = ' '.join([cur_track, cur_title, cur_length]) - rhs_comp = ' '.join([new_track, new_title, new_length]) - # Construct lhs and rhs arrays - lhs = { - 'disk': None, - 'track': lhs_track, - 'title': lhs_title, - 'length': lhs_length, - 'raw' : { - 'track': cur_track, - 'title': cur_title, - 'length': cur_length, + medium, disctitle = track_info.medium, track_info.disctitle + + # Track titles. + lhs_title, rhs_title = make_track_titles() + # Track number change. + lhs_track, rhs_track = make_track_numbers() + # Length change. + lhs_length, rhs_length = make_track_lengths() + # Penalties. + penalties = penalty_string(match.distance.tracks[track_info]) + + # Construct lhs and rhs arrays. + lhs = { + 'track': lhs_track, + 'title': lhs_title, + 'length': lhs_length, } - } - rhs = { - 'track': rhs_track, - 'title': rhs_title, - 'length': rhs_length, - 'penalties': penalty_string(match.distance.tracks[track_info]), - 'raw' : { - 'track': new_track, - 'title': new_title, - 'length': new_length, + rhs = { + 'track': rhs_track, + 'title': rhs_title, + 'length': rhs_length, + 'penalties': penalty_string(match.distance.tracks[track_info]), } - } - # Construct lhs and rhs line widths - lhs_width = len(lhs_comp) - rhs_width = len(rhs_comp) - - if lhs_comp != rhs_comp: - # Prefix changed tracks with U+2260: Not Equal To - prefix = ui.colorize('changed', '\u2260 ') - lines.append((prefix, lhs, rhs, lhs_width, rhs_width)) - elif config['import']['detail']: - # Prefix unchanged tracks with * - prefix = '* ' - lines.append((prefix, lhs, [], lhs_width, 0)) - - # Print each track in two columns, or across two lines. - joiner_width = len(''.join(['* ', ' -> '])) - tracklist_indent_width = \ - config['ui']['import']['indentation']['match_tracklist'].as_number() - indent = ui.indent(tracklist_indent_width) - col_width = (ui.term_width() - tracklist_indent_width - joiner_width) // 2 - if lines: - # Size columns. - max_width_l = max(lw for _, _, _, lw, _ in lines) - max_width_r = max(rw for _, _, _, _, rw in lines) - - if (max_width_l <= col_width) and (max_width_r <= col_width): - col_width_l = max_width_l - col_width_r = max_width_r - elif ((max_width_l > col_width) or (max_width_r > col_width)) \ - and ((max_width_l + max_width_r) <= col_width * 2): - # Either left or right column larger than allowed, but the other is - # smaller than allowed - in total the content fits. - col_width_l = max_width_l - col_width_r = max_width_r - else: - col_width_l = col_width - col_width_r = col_width - - # Print lines. - for prefix, lhs, rhs, lhs_width, rhs_width in lines: - l_pre = indent + prefix - r_pre = indent + ui.indent(len('* ')) - if not rhs: - if lhs['disk']: - print_(lhs['disk']) + + # Construct comparison strings to check for differences. + lhs_comp = ' '.join([cur_track, cur_title, cur_length]) + rhs_comp = ' '.join([new_track, new_title, new_length]) + # Construct lhs and rhs line widths. + lhs_width = len(lhs_comp) + rhs_width = len(rhs_comp) + + # Check whether track info will change should the user apply + # the match. + if lhs_comp != rhs_comp: + # Prefix changed tracks with U+2260: Not Equal To + prefix = ui.colorize('changed', '\u2260 ') + lines.append((prefix, lhs, rhs, lhs_width, rhs_width)) + elif config['import']['detail']: + # Prefix unchanged tracks with * + prefix = '* ' + lines.append((prefix, lhs, [], lhs_width, 0)) + + ### ----------------------------------------------------------------- + ### Print lines + ### ----------------------------------------------------------------- + + # Print each track in two columns, or across two lines. + joiner_width = len(''.join(['* ', ' -> '])) + tracklist_indent_width = \ + config['ui']['import']['indentation']['match_tracklist'].as_number() + indent = ui.indent(tracklist_indent_width) + col_width = (ui.term_width() - tracklist_indent_width - joiner_width) // 2 + if lines: + # Size columns. + max_width_l = max(lw for _, _, _, lw, _ in lines) + max_width_r = max(rw for _, _, _, _, rw in lines) + + if (max_width_l <= col_width) and (max_width_r <= col_width): + col_width_l = max_width_l + col_width_r = max_width_r + elif ((max_width_l > col_width) or (max_width_r > col_width)) \ + and ((max_width_l + max_width_r) <= col_width * 2): + # Either left or right column larger than allowed, but the other is + # smaller than allowed - in total the content fits. + col_width_l = max_width_l + col_width_r = max_width_r + else: + col_width_l = col_width + col_width_r = col_width + + # Print lines. + for prefix, lhs, rhs, lhs_width, rhs_width in lines: + l_pre = indent + prefix + r_pre = indent + ui.indent(len('* ')) + if not rhs: + if lhs['disk']: + print_(lhs['disk']) + else: + pad_l = ' ' * (max_width_l - lhs_width) + lhs_str = "{0} {1} {2}{3}".format( + lhs['track'], lhs['title'], pad_l, lhs['length']) + print_(l_pre + lhs_str) + elif (lhs_width > col_width_l) or (rhs_width > col_width_r): + layout = \ + config['ui']['import']['albumdiff']['layout'].as_choice({ + 'column': 0, + 'newline': 1, + }) + if layout == 0: + # Word wrapping inside columns. + format_track_as_columns(indent, prefix, + col_width_l, col_width_r, lhs, rhs) + elif layout == 1: + # Wrap overlong track changes at column border. + format_track(indent, prefix, lhs_width, rhs_width, + max_width_l, max_width_r, lhs, rhs) else: - pad_l = ' ' * (max_width_l - lhs_width) - lhs_str = "{0} {1} {2}{3}".format( + pad_l = ' ' * (col_width_l - lhs_width) + pad_r = ' ' * (col_width_r - rhs_width) + template = "{0} {1} {2}{3}" + lhs_str = template.format( lhs['track'], lhs['title'], pad_l, lhs['length']) - print_(l_pre + lhs_str) - elif (lhs_width > col_width_l) or (rhs_width > col_width_r): - layout = \ - config['ui']['import']['albumdiff']['layout'].as_choice({ - 'column': 0, - 'newline': 1, - }) - if layout == 0: - # Word wrapping inside columns. - format_track_as_columns(indent, prefix, - col_width_l, col_width_r, lhs, rhs) - elif layout == 1: - # Wrap overlong track changes at column border. - format_track(indent, prefix, lhs_width, rhs_width, - max_width_l, max_width_r, lhs, rhs) - else: - pad_l = ' ' * (col_width_l - lhs_width) - pad_r = ' ' * (col_width_r - rhs_width) - template = "{0} {1} {2}{3}" - lhs_str = template.format( - lhs['track'], lhs['title'], pad_l, lhs['length']) - rhs_str = template.format( - rhs['track'], rhs['title'], pad_r, rhs['length']) - print_(l_pre + u'%s -> %s' % (lhs_str, rhs_str)) - - # Missing and unmatched tracks. - if match.extra_tracks: - print_('Missing tracks ({0}/{1} - {2:.1%}):'.format( - len(match.extra_tracks), - len(match.info.tracks), - len(match.extra_tracks) / len(match.info.tracks) - )) - for track_info in match.extra_tracks: - line = ' ! %s (#%s)' % (track_info.title, format_index(track_info)) - if track_info.length: - line += ' (%s)' % ui.human_seconds_short(track_info.length) - print_(ui.colorize('text_warning', line)) - if match.extra_items: - print_('Unmatched tracks ({0}):'.format(len(match.extra_items))) - for item in match.extra_items: - line = ' ! %s (#%s)' % (item.title, format_index(item)) - if item.length: - line += ' (%s)' % ui.human_seconds_short(item.length) - print_(ui.colorize('text_warning', line)) + rhs_str = template.format( + rhs['track'], rhs['title'], pad_r, rhs['length']) + print_(l_pre + u'%s -> %s' % (lhs_str, rhs_str)) + + # Missing and unmatched tracks. + if match.extra_tracks: + print_('Missing tracks ({0}/{1} - {2:.1%}):'.format( + len(match.extra_tracks), + len(match.info.tracks), + len(match.extra_tracks) / len(match.info.tracks) + )) + for track_info in match.extra_tracks: + line = ' ! %s (#%s)' % (track_info.title, format_index(track_info)) + if track_info.length: + line += ' (%s)' % ui.human_seconds_short(track_info.length) + print_(ui.colorize('text_warning', line)) + if match.extra_items: + print_('Unmatched tracks ({0}):'.format(len(match.extra_items))) + for item in match.extra_items: + line = ' ! %s (#%s)' % (item.title, format_index(item)) + if item.length: + line += ' (%s)' % ui.human_seconds_short(item.length) + print_(ui.colorize('text_warning', line)) + + # Print the match header. + show_match_header() + + # Print the match details. + show_match_details() + + # Print the match tracks. + show_match_tracks() def show_item_change(item, match): From c88f56057251024a3d5b3149965f3c3d0c37dfbd Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Mon, 25 Jan 2016 17:28:10 +0100 Subject: [PATCH 25/45] fixup unicode errors --- beets/ui/commands.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index f8361ed846..05b28bec9e 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -429,7 +429,7 @@ def show_match_tracks(): suggests for them. """ def make_medium_info_line(): - """Construct a line with the current medium’s info.""" + """Construct a line with the current medium's info.""" media = match.info.media or 'Media' # Build output string. if match.info.mediums > 1 and track_info.disctitle: @@ -446,7 +446,7 @@ def make_track_titles(item, track_info): """ new_title = track_info.title if not item.title.strip(): - # If there's no title, we use the filename. Don’t colordiff. + # If there's no title, we use the filename. Don't colordiff. cur_title = displayable_path(os.path.basename(item.path)) return cur_title, new_title else: From f3c766d084e90f02845e1ab1f92007432a7311ee Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Mon, 25 Jan 2016 17:28:54 +0100 Subject: [PATCH 26/45] add uncolorize and color_len functions --- beets/ui/__init__.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 5d310b803f..8cb3105be5 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -524,6 +524,33 @@ def colorize(color_name, text): return text +def uncolorize(colored_text): + """Remove colors from a string. + """ + # Define a regular expression to match ANSI codes. + # See: http://stackoverflow.com/a/2187024/1382707 + # Explanation of regular expression: + # \x1b - matches ESC character + # \[ - matches opening square bracket + # [;\d]* - matches a sequence consisting of one or more digits or + # semicola + # [A-Za-z] - matches a letter + ansi_code_regex = re.compile(r"\x1b\[[;\d]*[A-Za-z]", re.VERBOSE) + # Strip ANSI codes from `colored_text` using the regular expression. + text = ansi_code_regex.sub(u'', colored_text) + return text + + +def color_len(colored_text): + """Measure the length of a string while excluding ANSI codes from the + measurement. The standard `len(my_string)` method also counts ANSI codes + to the string length, which is counterproductive when layouting a + Terminal interface. + """ + # Return the length of the uncolored string. + return len(uncolorize(colored_text)) + + def _colordiff(a, b, highlight='text_highlight', minor_highlight='text_highlight_minor'): """Given two values, return the same pair of strings except with From 7a8194c6f36267ee434e376f2422b8153e983b18 Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Mon, 25 Jan 2016 17:34:53 +0100 Subject: [PATCH 27/45] move column width calculation and line to new funs - change line data model: now tuple of (info, lhs, rhs) instead of (prefix, lhs, rhs, lhs_width, rhs_width). info is a dict --- beets/ui/commands.py | 164 ++++++++++++++++++++++++------------------- 1 file changed, 93 insertions(+), 71 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 05b28bec9e..53b0539d4c 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -504,6 +504,60 @@ def make_track_lengths(item, track_info): rhs_length = ui.colorize(new_length_color, new_length) return lhs_length, rhs_length + def calc_column_width(col_width, max_width_l, max_width_r): + """docstring for calc_column_width + """ + if (max_width_l <= col_width) and (max_width_r <= col_width): + col_width_l = max_width_l + col_width_r = max_width_r + elif ((max_width_l > col_width) or (max_width_r > col_width)) \ + and ((max_width_l + max_width_r) <= col_width * 2): + # Either left or right column larger than allowed, but the other is + # smaller than allowed - in total the content fits. + col_width_l = max_width_l + col_width_r = max_width_r + else: + col_width_l = col_width + col_width_r = col_width + return col_width_l, col_width_r + + def print_line(info, lhs, rhs): + """ + """ + l_pre = indent + info['prefix'] + r_pre = indent + ui.indent(len('* ')) + if not rhs: + if info['disk']: + print_(info['disk']) + else: + pad_l = ' ' * (max_width_l - lhs['width']) + lhs_str = "{0} {1} {2}{3}".format( + lhs['track'], lhs['title'], pad_l, lhs['length']) + print_(l_pre + lhs_str) + elif (lhs['width'] > col_width_l) or (rhs['width'] > col_width_r): + layout = \ + config['ui']['import']['albumdiff']['layout'].as_choice({ + 'column': 0, + 'newline': 1, + }) + if layout == 0: + # Word wrapping inside columns. + format_track_as_columns(indent, info['prefix'], + col_width_l, col_width_r, lhs, rhs) + elif layout == 1: + # Wrap overlong track changes at column border. + format_track(indent, info['prefix'], lhs['width'], rhs['width'], + max_width_l, max_width_r, lhs, rhs) + else: + pad_l = ' ' * (col_width_l - lhs['width']) + pad_r = ' ' * (col_width_r - rhs['width']) + template = "{0} {1} {2}{3}" + lhs_str = template.format( + lhs['track'], lhs['title'], pad_l, lhs['length']) + rhs_str = template.format( + rhs['track'], rhs['title'], pad_r, rhs['length']) + print_(l_pre + u'%s -> %s' % (lhs_str, rhs_str)) + # Read match detail indentation width from config. detail_indent = get_match_details_indentation() @@ -520,20 +574,22 @@ def make_track_lengths(item, track_info): # width is the length (in characters) of the uncolorized LHS. lines = [] medium = disctitle = None + max_width_l = max_width_r = 0 + for item, track_info in pairs: # If the track is the first on a new medium, show medium # number and title. if medium != track_info.medium or disctitle != track_info.disctitle: out = make_medium_info_line() - lhs = { - 'disk': detail_indent + out, - 'track': None, - 'title': None, - 'length': None, - 'raw': None + info = { + 'prefix': u'', + 'disk': detail_indent + out, + 'penalties': None, } - lines.append(('', lhs, '', 0, 0)) + lhs = {} + rhs = {} + lines.append((info, lhs, rhs)) medium, disctitle = track_info.medium, track_info.disctitle # Track titles. @@ -545,100 +601,66 @@ def make_track_lengths(item, track_info): # Penalties. penalties = penalty_string(match.distance.tracks[track_info]) + # Construct comparison strings to check for differences. + lhs_comp = ui.uncolorize(' '.join([lhs_track, lhs_title, lhs_length])) + rhs_comp = ui.uncolorize(' '.join([rhs_track, rhs_title, rhs_length])) + # Construct lhs and rhs line widths. + lhs_width = len(lhs_comp) + rhs_width = len(rhs_comp) + if max_width_l < lhs_width: max_width_l = lhs_width + if max_width_r < rhs_width: max_width_r = rhs_width + # Construct lhs and rhs arrays. + info = { + 'prefix': u'', + 'disk': None, + 'penalties': penalty_string(match.distance.tracks[track_info]), + } lhs = { 'track': lhs_track, 'title': lhs_title, 'length': lhs_length, + 'width': lhs_width, } rhs = { 'track': rhs_track, 'title': rhs_title, 'length': rhs_length, - 'penalties': penalty_string(match.distance.tracks[track_info]), + 'width': rhs_width, } - # Construct comparison strings to check for differences. - lhs_comp = ' '.join([cur_track, cur_title, cur_length]) - rhs_comp = ' '.join([new_track, new_title, new_length]) - # Construct lhs and rhs line widths. - lhs_width = len(lhs_comp) - rhs_width = len(rhs_comp) - # Check whether track info will change should the user apply # the match. if lhs_comp != rhs_comp: # Prefix changed tracks with U+2260: Not Equal To - prefix = ui.colorize('changed', '\u2260 ') - lines.append((prefix, lhs, rhs, lhs_width, rhs_width)) + info['prefix'] = ui.colorize('changed', '\u2260 ') + lines.append((info, lhs, rhs)) elif config['import']['detail']: # Prefix unchanged tracks with * - prefix = '* ' - lines.append((prefix, lhs, [], lhs_width, 0)) + info['prefix'] = '* ' + lines.append((info, lhs, {})) ### ----------------------------------------------------------------- ### Print lines ### ----------------------------------------------------------------- - # Print each track in two columns, or across two lines. joiner_width = len(''.join(['* ', ' -> '])) tracklist_indent_width = \ config['ui']['import']['indentation']['match_tracklist'].as_number() indent = ui.indent(tracklist_indent_width) col_width = (ui.term_width() - tracklist_indent_width - joiner_width) // 2 + if lines: - # Size columns. - max_width_l = max(lw for _, _, _, lw, _ in lines) - max_width_r = max(rw for _, _, _, _, rw in lines) - - if (max_width_l <= col_width) and (max_width_r <= col_width): - col_width_l = max_width_l - col_width_r = max_width_r - elif ((max_width_l > col_width) or (max_width_r > col_width)) \ - and ((max_width_l + max_width_r) <= col_width * 2): - # Either left or right column larger than allowed, but the other is - # smaller than allowed - in total the content fits. - col_width_l = max_width_l - col_width_r = max_width_r - else: - col_width_l = col_width - col_width_r = col_width - + # Calculate width of left and right column. + col_width_l, col_width_r = \ + calc_column_width(col_width, max_width_l, max_width_r) # Print lines. - for prefix, lhs, rhs, lhs_width, rhs_width in lines: - l_pre = indent + prefix - r_pre = indent + ui.indent(len('* ')) - if not rhs: - if lhs['disk']: - print_(lhs['disk']) - else: - pad_l = ' ' * (max_width_l - lhs_width) - lhs_str = "{0} {1} {2}{3}".format( - lhs['track'], lhs['title'], pad_l, lhs['length']) - print_(l_pre + lhs_str) - elif (lhs_width > col_width_l) or (rhs_width > col_width_r): - layout = \ - config['ui']['import']['albumdiff']['layout'].as_choice({ - 'column': 0, - 'newline': 1, - }) - if layout == 0: - # Word wrapping inside columns. - format_track_as_columns(indent, prefix, - col_width_l, col_width_r, lhs, rhs) - elif layout == 1: - # Wrap overlong track changes at column border. - format_track(indent, prefix, lhs_width, rhs_width, - max_width_l, max_width_r, lhs, rhs) - else: - pad_l = ' ' * (col_width_l - lhs_width) - pad_r = ' ' * (col_width_r - rhs_width) - template = "{0} {1} {2}{3}" - lhs_str = template.format( - lhs['track'], lhs['title'], pad_l, lhs['length']) - rhs_str = template.format( - rhs['track'], rhs['title'], pad_r, rhs['length']) - print_(l_pre + u'%s -> %s' % (lhs_str, rhs_str)) + for info, lhs, rhs in lines: + print_line(info, lhs, rhs) + + ### ----------------------------------------------------------------- + ### Missing and unmatched tracks + ### ----------------------------------------------------------------- # Missing and unmatched tracks. if match.extra_tracks: From d36fcec095d79164bc153ec19658709db365db08 Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Mon, 25 Jan 2016 17:35:19 +0100 Subject: [PATCH 28/45] minor fixup and reordering --- beets/ui/commands.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 53b0539d4c..da0e77cea9 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -593,11 +593,11 @@ def print_line(info, lhs, rhs): medium, disctitle = track_info.medium, track_info.disctitle # Track titles. - lhs_title, rhs_title = make_track_titles() + lhs_title, rhs_title = make_track_titles(item, track_info) # Track number change. - lhs_track, rhs_track = make_track_numbers() + lhs_track, rhs_track = make_track_numbers(item, track_info) # Length change. - lhs_length, rhs_length = make_track_lengths() + lhs_length, rhs_length = make_track_lengths(item, track_info) # Penalties. penalties = penalty_string(match.distance.tracks[track_info]) @@ -617,14 +617,14 @@ def print_line(info, lhs, rhs): 'penalties': penalty_string(match.distance.tracks[track_info]), } lhs = { - 'track': lhs_track, 'title': lhs_title, + 'track': lhs_track, 'length': lhs_length, 'width': lhs_width, } rhs = { - 'track': rhs_track, 'title': rhs_title, + 'track': rhs_track, 'length': rhs_length, 'width': rhs_width, } From 55493a160531e64dec7889b4f04160dd644cd8ac Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Wed, 27 Jan 2016 14:46:06 +0100 Subject: [PATCH 29/45] fixup comment --- beets/ui/commands.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index da0e77cea9..f1988c4df8 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -570,8 +570,7 @@ def print_line(info, lhs, rhs): ### ----------------------------------------------------------------- # Build up LHS and RHS for track difference display. The `lines` list - # contains ``prefix, lhs, rhs, lhs_width, rhs_width`` tuples where - # width is the length (in characters) of the uncolorized LHS. + # contains `(info, lhs, rhs)` tuples. lines = [] medium = disctitle = None max_width_l = max_width_r = 0 From 1584c0720cb6e347895fa9f0d70fce3de29372ad Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Thu, 18 Feb 2016 19:46:40 +0100 Subject: [PATCH 30/45] move two methods into show_match_tracks() --- beets/ui/commands.py | 245 +++++++++++++++++++++---------------------- 1 file changed, 122 insertions(+), 123 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index f1988c4df8..9d137718e8 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -228,129 +228,6 @@ def format_index(track_info): else: return unicode(index) - def format_track(indent, prefix, lhs_width, rhs_width, col_width_l, col_width_r, lhs, rhs): - """docstring for format_track""" - # Print track - pad_l = u' ' * (col_width_l - lhs_width) - pad_r = u' ' * (col_width_r - rhs_width) - template = "{0} {1} {2}{3}" - lhs_str = template.format( - lhs['track'], lhs['title'], pad_l, lhs['length']) - rhs_str = template.format( - rhs['track'], rhs['title'], pad_r, rhs['length']) - print_(u'{0}{1} ->\n{2}{3}'.format(indent + prefix, lhs_str, indent + ui.indent(len('* ')), rhs_str)) - - def format_track_as_columns(indent, prefix, col_width_l, col_width_r, - lhs, rhs): - """docstring for format_track_as_columns""" - # Calculate available space for word wrapping. - # Left-hand side. - lhs_track_len = len(lhs['raw']['track']) - lhs_length_len = len(lhs['raw']['length']) - if lhs_track_len > 0: lhs_track_len += 1 # Space. - if lhs_length_len > 0: lhs_length_len += 1 # Space. - lhs_used_first = lhs_track_len + lhs_length_len - lhs_used_middle = lhs_track_len - lhs_used_last = lhs_track_len - col_width_l_first = col_width_l - lhs_used_first - col_width_l_middle = col_width_l - lhs_used_middle - col_width_l_last = col_width_l - lhs_used_last - - # Right-hand side. - rhs_track_len = len(rhs['raw']['track']) - rhs_length_len = len(rhs['raw']['length']) - if rhs_track_len > 0: rhs_track_len += 1 # Space. - if rhs_length_len > 0: rhs_length_len += 1 # Space. - rhs_used_first = rhs_track_len + rhs_length_len - rhs_used_middle = rhs_track_len - rhs_used_last = rhs_track_len - col_width_r_first = col_width_r - rhs_used_first - col_width_r_middle = col_width_r - rhs_used_middle - col_width_r_last = col_width_r - rhs_used_last - - # Calculate word wrapping. - lhs_lines = ui.split_into_lines(lhs['title'], lhs['raw']['title'], - col_width_l_first, col_width_l_middle, col_width_l_last) - rhs_lines = ui.split_into_lines(rhs['title'], rhs['raw']['title'], - col_width_r_first, col_width_r_middle, col_width_r_last) - - # Construct string for all lines of both columns. - max_line_count = max(len(lhs_lines['col']), len(rhs_lines['col'])) - align_length_l = len(lhs['raw']['length']) - align_length_r = len(rhs['raw']['length']) - out = u'' - for i in range(max_line_count): - # Indentation - out += indent - - # Prefix. - if i == 0: - out += prefix - else: - out += ui.indent(len('* ')) - - # Track number or alignment - if i == 0 and lhs_track_len > 0: - out += lhs['track'] + ' ' - else: - out += ' ' * lhs_track_len - - # Line i of lhs track title. - if i in range(len(lhs_lines['col'])): - out += lhs_lines['col'][i] - - # Alignment up to the end of the left column. - if i in range(len(lhs_lines['raw'])): - align_title = len(lhs_lines['raw'][i]) - else: - align_title = 0 - align_used = lhs_track_len + align_title - if i == 0: - align_used += align_length_l - padding = col_width_l - align_used - out += ' ' * padding - - # Length in first line. - if i == 0: - out += lhs['length'] - - # Arrow between columns. - if i == 0: - out += u' -> ' - else: - out += u' ' # u' .. ' - - # Track number or alignment. - if i == 0 and rhs_track_len > 0: - out += rhs['track'] + ' ' - else: - out += ' ' * rhs_track_len - - # Line i of rhs track title. - if i in range(len(rhs_lines['col'])): - out += rhs_lines['col'][i] - - # Alignment up to the end of the right column. - if i in range(len(rhs_lines['raw'])): - align_title = len(rhs_lines['raw'][i]) - else: - align_title = 0 - align_used = rhs_track_len + align_title - if i == 0: - align_used += align_length_r - padding = col_width_r - align_used - out += ' ' * padding - - # Length in first line. - if i == 0: - out += rhs['length'] - - # Linebreak, except in the last line. - if i < max_line_count-1: - out += u'\n' - # Print complete line. - print_(out) - def show_match_header(): """Print out a 'header' identifying the suggested match (album name, artist name,...) and summarizing the changes that would be made should @@ -521,6 +398,128 @@ def calc_column_width(col_width, max_width_l, max_width_r): col_width_r = col_width return col_width_l, col_width_r + def format_track(indent, prefix, lhs_width, rhs_width, col_width_l, col_width_r, lhs, rhs): + """docstring for format_track""" + # Print track + pad_l = u' ' * (col_width_l - lhs_width) + pad_r = u' ' * (col_width_r - rhs_width) + template = "{0} {1} {2}{3}" + lhs_str = template.format( + lhs['track'], lhs['title'], pad_l, lhs['length']) + rhs_str = template.format( + rhs['track'], rhs['title'], pad_r, rhs['length']) + print_(u'{0}{1} ->\n{2}{3}'.format(indent + prefix, lhs_str, indent + ui.indent(len('* ')), rhs_str)) + + def format_track_as_columns(indent, prefix, col_width_l, col_width_r, lhs, rhs): + """docstring for format_track_as_columns""" + # Calculate available space for word wrapping. + # Left-hand side. + lhs_track_len = len(lhs['raw']['track']) + lhs_length_len = len(lhs['raw']['length']) + if lhs_track_len > 0: lhs_track_len += 1 # Space. + if lhs_length_len > 0: lhs_length_len += 1 # Space. + lhs_used_first = lhs_track_len + lhs_length_len + lhs_used_middle = lhs_track_len + lhs_used_last = lhs_track_len + col_width_l_first = col_width_l - lhs_used_first + col_width_l_middle = col_width_l - lhs_used_middle + col_width_l_last = col_width_l - lhs_used_last + + # Right-hand side. + rhs_track_len = len(rhs['raw']['track']) + rhs_length_len = len(rhs['raw']['length']) + if rhs_track_len > 0: rhs_track_len += 1 # Space. + if rhs_length_len > 0: rhs_length_len += 1 # Space. + rhs_used_first = rhs_track_len + rhs_length_len + rhs_used_middle = rhs_track_len + rhs_used_last = rhs_track_len + col_width_r_first = col_width_r - rhs_used_first + col_width_r_middle = col_width_r - rhs_used_middle + col_width_r_last = col_width_r - rhs_used_last + + # Calculate word wrapping. + lhs_lines = ui.split_into_lines(lhs['title'], lhs['raw']['title'], + col_width_l_first, col_width_l_middle, col_width_l_last) + rhs_lines = ui.split_into_lines(rhs['title'], rhs['raw']['title'], + col_width_r_first, col_width_r_middle, col_width_r_last) + + # Construct string for all lines of both columns. + max_line_count = max(len(lhs_lines['col']), len(rhs_lines['col'])) + align_length_l = len(lhs['raw']['length']) + align_length_r = len(rhs['raw']['length']) + out = u'' + for i in range(max_line_count): + # Indentation + out += indent + + # Prefix. + if i == 0: + out += prefix + else: + out += ui.indent(len('* ')) + + # Track number or alignment + if i == 0 and lhs_track_len > 0: + out += lhs['track'] + ' ' + else: + out += ' ' * lhs_track_len + + # Line i of lhs track title. + if i in range(len(lhs_lines['col'])): + out += lhs_lines['col'][i] + + # Alignment up to the end of the left column. + if i in range(len(lhs_lines['raw'])): + align_title = len(lhs_lines['raw'][i]) + else: + align_title = 0 + align_used = lhs_track_len + align_title + if i == 0: + align_used += align_length_l + padding = col_width_l - align_used + out += ' ' * padding + + # Length in first line. + if i == 0: + out += lhs['length'] + + # Arrow between columns. + if i == 0: + out += u' -> ' + else: + out += u' ' # u' .. ' + + # Track number or alignment. + if i == 0 and rhs_track_len > 0: + out += rhs['track'] + ' ' + else: + out += ' ' * rhs_track_len + + # Line i of rhs track title. + if i in range(len(rhs_lines['col'])): + out += rhs_lines['col'][i] + + # Alignment up to the end of the right column. + if i in range(len(rhs_lines['raw'])): + align_title = len(rhs_lines['raw'][i]) + else: + align_title = 0 + align_used = rhs_track_len + align_title + if i == 0: + align_used += align_length_r + padding = col_width_r - align_used + out += ' ' * padding + + # Length in first line. + if i == 0: + out += rhs['length'] + + # Linebreak, except in the last line. + if i < max_line_count-1: + out += u'\n' + # Print complete line. + print_(out) + def print_line(info, lhs, rhs): """ """ From 9a50224500b0b8c7859b955115fe99c101672f6a Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Thu, 18 Feb 2016 19:47:14 +0100 Subject: [PATCH 31/45] remove unused show_album(artist, album) --- beets/ui/commands.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 9d137718e8..1171d7abc9 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -198,15 +198,6 @@ def show_change(cur_artist, cur_album, match): album's tags are changed according to `match`, which must be an AlbumMatch object. """ - def show_album(artist, album): - if artist: - album_description = u' %s - %s' % (artist, album) - elif album: - album_description = u' %s' % album - else: - album_description = u' (unknown album)' - print_(album_description) - def format_index(track_info): """Return a string representing the track index of the given TrackInfo or Item object. From 0b1ba738bae1c6a5c90ec4125ced51cf8b3760c2 Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Thu, 18 Feb 2016 19:47:35 +0100 Subject: [PATCH 32/45] minor whitespace change --- beets/ui/commands.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 1171d7abc9..317af8659c 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -596,8 +596,10 @@ def print_line(info, lhs, rhs): # Construct lhs and rhs line widths. lhs_width = len(lhs_comp) rhs_width = len(rhs_comp) - if max_width_l < lhs_width: max_width_l = lhs_width - if max_width_r < rhs_width: max_width_r = rhs_width + if max_width_l < lhs_width: + max_width_l = lhs_width + if max_width_r < rhs_width: + max_width_r = rhs_width # Construct lhs and rhs arrays. info = { From 1b424db7bc6a3075581e158c52a1d6913abd5a37 Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Fri, 19 Feb 2016 12:59:04 +0100 Subject: [PATCH 33/45] restructure format_tracks_as_columns --- beets/ui/__init__.py | 8 ++-- beets/ui/commands.py | 94 +++++++++++++++++++++++++++----------------- 2 files changed, 63 insertions(+), 39 deletions(-) diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 8cb3105be5..d8fb912773 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -682,7 +682,7 @@ def term_width(): return width -def split_into_lines(string, raw_string, first_width, middle_width, last_width): +def split_into_lines(string, raw_string, width_tuple): """Splits string into substrings at whitespace. The first substring has a length not longer than first_width, the last substring has a length not longer than last_width, and all other substrings have a length not longer @@ -691,6 +691,8 @@ def split_into_lines(string, raw_string, first_width, middle_width, last_width): string contains ANSI codes at word borders. Use raw_string to find substrings, but return the words of string. """ + first_width, middle_width, last_width = width_tuple + words_raw = raw_string.split() words = string.split() assert len(words_raw) == len(words) @@ -705,9 +707,9 @@ def split_into_lines(string, raw_string, first_width, middle_width, last_width): else: pot_substr_raw = ' '.join([next_substr_raw, words_raw[i]]) pot_substr = ' '.join([next_substr, words[i]]) - + #print_('pot_substr_raw: {}'.format(pot_substr_raw)) - + # Find out if pot(ential)_substr fits into next substring fits_first = \ (len(result['raw']) == 0 and len(pot_substr_raw) <= first_width) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 317af8659c..5ba0ace14c 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -403,41 +403,63 @@ def format_track(indent, prefix, lhs_width, rhs_width, col_width_l, col_width_r, def format_track_as_columns(indent, prefix, col_width_l, col_width_r, lhs, rhs): """docstring for format_track_as_columns""" - # Calculate available space for word wrapping. - # Left-hand side. - lhs_track_len = len(lhs['raw']['track']) - lhs_length_len = len(lhs['raw']['length']) - if lhs_track_len > 0: lhs_track_len += 1 # Space. - if lhs_length_len > 0: lhs_length_len += 1 # Space. - lhs_used_first = lhs_track_len + lhs_length_len - lhs_used_middle = lhs_track_len - lhs_used_last = lhs_track_len - col_width_l_first = col_width_l - lhs_used_first - col_width_l_middle = col_width_l - lhs_used_middle - col_width_l_last = col_width_l - lhs_used_last - - # Right-hand side. - rhs_track_len = len(rhs['raw']['track']) - rhs_length_len = len(rhs['raw']['length']) - if rhs_track_len > 0: rhs_track_len += 1 # Space. - if rhs_length_len > 0: rhs_length_len += 1 # Space. - rhs_used_first = rhs_track_len + rhs_length_len - rhs_used_middle = rhs_track_len - rhs_used_last = rhs_track_len - col_width_r_first = col_width_r - rhs_used_first - col_width_r_middle = col_width_r - rhs_used_middle - col_width_r_last = col_width_r - rhs_used_last + # TODO: Think about how to beautify calc_available_columns_per_line + # and ui.split_into_lines, especially with regard to the + # available cols tuple (first, middle, last). + def calc_available_columns_per_line(col_width, track_num_len, track_duration_len): + """Calculate the available space in columns for the track title + for the first, all middle, and the last line.""" + # Account for space between title and number/duration. + if track_num_len > 0: track_num_len += 1 + if track_duration_len > 0: track_duration_len += 1 + # Calculate the columns already in use for track number and + # track duration. + used_first = track_num_len + track_duration_len + used_middle = track_num_len + used_last = track_num_len + # Calculate the available columns for the track title. + col_width_first = col_width - used_first + col_width_middle = col_width - used_middle + col_width_last = col_width - used_last + return col_width_first, col_width_middle, col_width_last + + def calc_word_wrapping(col_width, xhs): + """docstring for calc_word_wrapping""" + # Calculate available space for word wrapping. + available_cols = calc_available_columns_per_line( + col_width, + xhs['len']['track'], + xhs['len']['length'] + ) + # Calculate word wrapping. + xhs_lines = ui.split_into_lines( + xhs['title'], + xhs['uncolored']['title'], + available_cols + ) + return xhs_lines + + # Uncolorize and measure colored strings. + # TODO: Get rid of this. + lhs['len'] = {} + lhs['len']['track'] = ui.color_len(lhs['track']) + lhs['len']['length'] = ui.color_len(lhs['length']) + lhs['uncolored'] = {} + lhs['uncolored']['title'] = ui.uncolorize(lhs['title']) + rhs['len'] = {} + rhs['len']['track'] = ui.color_len(rhs['track']) + rhs['len']['length'] = ui.color_len(rhs['length']) + rhs['uncolored'] = {} + rhs['uncolored']['title'] = ui.uncolorize(rhs['title']) # Calculate word wrapping. - lhs_lines = ui.split_into_lines(lhs['title'], lhs['raw']['title'], - col_width_l_first, col_width_l_middle, col_width_l_last) - rhs_lines = ui.split_into_lines(rhs['title'], rhs['raw']['title'], - col_width_r_first, col_width_r_middle, col_width_r_last) + lhs_lines = calc_word_wrapping(col_width_l, lhs) + rhs_lines = calc_word_wrapping(col_width_r, rhs) # Construct string for all lines of both columns. max_line_count = max(len(lhs_lines['col']), len(rhs_lines['col'])) - align_length_l = len(lhs['raw']['length']) - align_length_r = len(rhs['raw']['length']) + align_length_l = lhs['len']['length'] + align_length_r = rhs['len']['length'] out = u'' for i in range(max_line_count): # Indentation @@ -450,10 +472,10 @@ def format_track_as_columns(indent, prefix, col_width_l, col_width_r, lhs, rhs): out += ui.indent(len('* ')) # Track number or alignment - if i == 0 and lhs_track_len > 0: + if i == 0 and lhs['len']['track'] > 0: out += lhs['track'] + ' ' else: - out += ' ' * lhs_track_len + out += ' ' * lhs['len']['track'] # Line i of lhs track title. if i in range(len(lhs_lines['col'])): @@ -464,7 +486,7 @@ def format_track_as_columns(indent, prefix, col_width_l, col_width_r, lhs, rhs): align_title = len(lhs_lines['raw'][i]) else: align_title = 0 - align_used = lhs_track_len + align_title + align_used = lhs['len']['track'] + align_title if i == 0: align_used += align_length_l padding = col_width_l - align_used @@ -481,10 +503,10 @@ def format_track_as_columns(indent, prefix, col_width_l, col_width_r, lhs, rhs): out += u' ' # u' .. ' # Track number or alignment. - if i == 0 and rhs_track_len > 0: + if i == 0 and rhs['len']['track'] > 0: out += rhs['track'] + ' ' else: - out += ' ' * rhs_track_len + out += ' ' * rhs['len']['track'] # Line i of rhs track title. if i in range(len(rhs_lines['col'])): @@ -495,7 +517,7 @@ def format_track_as_columns(indent, prefix, col_width_l, col_width_r, lhs, rhs): align_title = len(rhs_lines['raw'][i]) else: align_title = 0 - align_used = rhs_track_len + align_title + align_used = lhs['len']['track'] + align_title if i == 0: align_used += align_length_r padding = col_width_r - align_used From 8f578e4495a957fd1c3b441064ab8be19a97a63a Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Fri, 19 Feb 2016 12:59:53 +0100 Subject: [PATCH 34/45] new make_line method - move construction of lhs and rhs dicts to make_line - move make_track_titles, make_track_numbers, and make_track_lengths to make_line --- beets/ui/commands.py | 228 ++++++++++++++++++++++--------------------- 1 file changed, 118 insertions(+), 110 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 5ba0ace14c..04aba17375 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -309,68 +309,117 @@ def make_medium_info_line(): out = '* %s %s' % (media, track_info.medium) return out - def make_track_titles(item, track_info): - """docstring for fname - """ - new_title = track_info.title - if not item.title.strip(): - # If there's no title, we use the filename. Don't colordiff. - cur_title = displayable_path(os.path.basename(item.path)) - return cur_title, new_title - else: - # If there is a title, highlight differences. - cur_title = item.title.strip() - return ui.colordiff(cur_title, new_title) - - def make_track_numbers(item, track_info): - """docstring for fname - """ - cur_track = format_index(item) - new_track = format_index(track_info) - if cur_track != new_track: - if item.track in (track_info.index, track_info.medium_index): - cur_track_templ = u'(#{})' - new_track_templ = u'(#{})' - cur_track_color = 'text_highlight_minor' - new_track_color = 'text_highlight_minor' + def make_line(item, track_info): + """docstring for make_track_line""" + def make_track_titles(item, track_info): + """docstring for fname + """ + new_title = track_info.title + if not item.title.strip(): + # If there's no title, we use the filename. Don't colordiff. + cur_title = displayable_path(os.path.basename(item.path)) + return cur_title, new_title else: - cur_track_templ = u'(#{})' - new_track_templ = u'(#{})' - cur_track_color = 'text_highlight' - new_track_color = 'text_highlight' - else: - cur_track_templ = u'' - new_track_templ = u'' - cur_track_color = 'text_faint' - new_track_color = 'text_faint' - cur_track = cur_track_templ.format(cur_track) - new_track = new_track_templ.format(new_track) - lhs_track = ui.colorize(cur_track_color, cur_track) - rhs_track = ui.colorize(new_track_color, new_track) - return lhs_track, rhs_track - - def make_track_lengths(item, track_info): - """ - """ - if item.length and track_info.length and \ - abs(item.length - track_info.length) > \ - config['ui']['length_diff_thresh'].as_number(): - cur_length_templ = u'({})' - new_length_templ = u'({})' - cur_length_color = 'text_highlight' - new_length_color = 'text_highlight' - else: - cur_length_templ = u'({})' - new_length_templ = u'({})' - cur_length_color = 'text_highlight_minor' - new_length_color = 'text_highlight_minor' - cur_length0 = ui.human_seconds_short(item.length) - new_length0 = ui.human_seconds_short(track_info.length) - cur_length = cur_length_templ.format(cur_length0) - new_length = new_length_templ.format(new_length0) - lhs_length = ui.colorize(cur_length_color, cur_length) - rhs_length = ui.colorize(new_length_color, new_length) - return lhs_length, rhs_length + # If there is a title, highlight differences. + cur_title = item.title.strip() + return ui.colordiff(cur_title, new_title) + + def make_track_numbers(item, track_info): + """docstring for fname + """ + cur_track = format_index(item) + new_track = format_index(track_info) + if cur_track != new_track: + if item.track in (track_info.index, track_info.medium_index): + cur_track_templ = u'(#{})' + new_track_templ = u'(#{})' + cur_track_color = 'text_highlight_minor' + new_track_color = 'text_highlight_minor' + else: + cur_track_templ = u'(#{})' + new_track_templ = u'(#{})' + cur_track_color = 'text_highlight' + new_track_color = 'text_highlight' + else: + cur_track_templ = u'' + new_track_templ = u'' + cur_track_color = 'text_faint' + new_track_color = 'text_faint' + cur_track = cur_track_templ.format(cur_track) + new_track = new_track_templ.format(new_track) + lhs_track = ui.colorize(cur_track_color, cur_track) + rhs_track = ui.colorize(new_track_color, new_track) + return lhs_track, rhs_track + + def make_track_lengths(item, track_info): + """ + """ + if item.length and track_info.length and \ + abs(item.length - track_info.length) > \ + config['ui']['length_diff_thresh'].as_number(): + cur_length_templ = u'({})' + new_length_templ = u'({})' + cur_length_color = 'text_highlight' + new_length_color = 'text_highlight' + else: + cur_length_templ = u'({})' + new_length_templ = u'({})' + cur_length_color = 'text_highlight_minor' + new_length_color = 'text_highlight_minor' + cur_length0 = ui.human_seconds_short(item.length) + new_length0 = ui.human_seconds_short(track_info.length) + cur_length = cur_length_templ.format(cur_length0) + new_length = new_length_templ.format(new_length0) + lhs_length = ui.colorize(cur_length_color, cur_length) + rhs_length = ui.colorize(new_length_color, new_length) + return lhs_length, rhs_length + + # Track titles. + lhs_title, rhs_title = make_track_titles(item, track_info) + # Track number change. + lhs_track, rhs_track = make_track_numbers(item, track_info) + # Length change. + lhs_length, rhs_length = make_track_lengths(item, track_info) + # Penalties. + penalties = penalty_string(match.distance.tracks[track_info]) + + # Construct comparison strings to check for differences and update + # line length. + lhs_comp = ui.uncolorize(' '.join([lhs_track, lhs_title, lhs_length])) + rhs_comp = ui.uncolorize(' '.join([rhs_track, rhs_title, rhs_length])) + lhs_width = len(lhs_comp) + rhs_width = len(rhs_comp) + + # Construct lhs and rhs dicts. + info = { + 'prefix': u'', + 'disk': None, + 'penalties': penalty_string(match.distance.tracks[track_info]), + } + lhs = { + 'title': lhs_title, + 'track': lhs_track, + 'length': lhs_length, + 'width': lhs_width, + } + rhs = { + 'title': rhs_title, + 'track': rhs_track, + 'length': rhs_length, + 'width': rhs_width, + } + + # Check whether track info will change should the user apply + # the match. + # TODO: Is there a better way to determine if a track has changed? + if lhs_comp != rhs_comp: + # Prefix changed tracks with U+2260: Not Equal To + info['prefix'] = ui.colorize('changed', '\u2260 ') + return (info, lhs, rhs) + elif config['import']['detail']: + # Prefix unchanged tracks with * + info['prefix'] = '* ' + return (info, lhs, {}) def calc_column_width(col_width, max_width_l, max_width_r): """docstring for calc_column_width @@ -588,7 +637,6 @@ def print_line(info, lhs, rhs): max_width_l = max_width_r = 0 for item, track_info in pairs: - # If the track is the first on a new medium, show medium # number and title. if medium != track_info.medium or disctitle != track_info.disctitle: @@ -603,55 +651,15 @@ def print_line(info, lhs, rhs): lines.append((info, lhs, rhs)) medium, disctitle = track_info.medium, track_info.disctitle - # Track titles. - lhs_title, rhs_title = make_track_titles(item, track_info) - # Track number change. - lhs_track, rhs_track = make_track_numbers(item, track_info) - # Length change. - lhs_length, rhs_length = make_track_lengths(item, track_info) - # Penalties. - penalties = penalty_string(match.distance.tracks[track_info]) - - # Construct comparison strings to check for differences. - lhs_comp = ui.uncolorize(' '.join([lhs_track, lhs_title, lhs_length])) - rhs_comp = ui.uncolorize(' '.join([rhs_track, rhs_title, rhs_length])) - # Construct lhs and rhs line widths. - lhs_width = len(lhs_comp) - rhs_width = len(rhs_comp) - if max_width_l < lhs_width: - max_width_l = lhs_width - if max_width_r < rhs_width: - max_width_r = rhs_width - - # Construct lhs and rhs arrays. - info = { - 'prefix': u'', - 'disk': None, - 'penalties': penalty_string(match.distance.tracks[track_info]), - } - lhs = { - 'title': lhs_title, - 'track': lhs_track, - 'length': lhs_length, - 'width': lhs_width, - } - rhs = { - 'title': rhs_title, - 'track': rhs_track, - 'length': rhs_length, - 'width': rhs_width, - } + # Construct the line tuple for the track. + info, lhs, rhs = make_line(item, track_info) + lines.append((info, lhs, rhs)) - # Check whether track info will change should the user apply - # the match. - if lhs_comp != rhs_comp: - # Prefix changed tracks with U+2260: Not Equal To - info['prefix'] = ui.colorize('changed', '\u2260 ') - lines.append((info, lhs, rhs)) - elif config['import']['detail']: - # Prefix unchanged tracks with * - info['prefix'] = '* ' - lines.append((info, lhs, {})) + # Update lhs and rhs maximum line widths. + if max_width_l < lhs['width']: + max_width_l = lhs['width'] + if max_width_r < rhs['width']: + max_width_r = rhs['width'] ### ----------------------------------------------------------------- ### Print lines From 36f53ef00ba4970f1fba74453e50ca095b73a0cb Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Fri, 19 Feb 2016 17:20:42 +0100 Subject: [PATCH 35/45] move format_index to show_match_tracks --- beets/ui/commands.py | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 04aba17375..995a01ecfc 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -198,27 +198,6 @@ def show_change(cur_artist, cur_album, match): album's tags are changed according to `match`, which must be an AlbumMatch object. """ - def format_index(track_info): - """Return a string representing the track index of the given - TrackInfo or Item object. - """ - if isinstance(track_info, hooks.TrackInfo): - index = track_info.index - medium_index = track_info.medium_index - medium = track_info.medium - mediums = match.info.mediums - else: - index = medium_index = track_info.track - medium = track_info.disc - mediums = track_info.disctotal - if config['per_disc_numbering']: - if mediums > 1: - return u'{0}-{1}'.format(medium, medium_index) - else: - return unicode(medium_index) - else: - return unicode(index) - def show_match_header(): """Print out a 'header' identifying the suggested match (album name, artist name,...) and summarizing the changes that would be made should @@ -438,6 +417,27 @@ def calc_column_width(col_width, max_width_l, max_width_r): col_width_r = col_width return col_width_l, col_width_r + def format_index(track_info): + """Return a string representing the track index of the given + TrackInfo or Item object. + """ + if isinstance(track_info, hooks.TrackInfo): + index = track_info.index + medium_index = track_info.medium_index + medium = track_info.medium + mediums = match.info.mediums + else: + index = medium_index = track_info.track + medium = track_info.disc + mediums = track_info.disctotal + if config['per_disc_numbering']: + if mediums > 1: + return u'{0}-{1}'.format(medium, medium_index) + else: + return unicode(medium_index) + else: + return unicode(index) + def format_track(indent, prefix, lhs_width, rhs_width, col_width_l, col_width_r, lhs, rhs): """docstring for format_track""" # Print track From 87c38c18e90271e7a979f52f370eebddfcb04730 Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Sat, 20 Feb 2016 16:46:18 +0100 Subject: [PATCH 36/45] add indentation to info dict - add 'changed' key to info dict to indicate whether rhs should be displayed at all --- beets/ui/commands.py | 124 +++++++++++++++++++++++++++---------------- 1 file changed, 77 insertions(+), 47 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 995a01ecfc..b55ebf6788 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -369,10 +369,16 @@ def make_track_lengths(item, track_info): lhs_width = len(lhs_comp) rhs_width = len(rhs_comp) + # Construct indentation. + indent_width = \ + config['ui']['import']['indentation']['match_tracklist'].as_number() + indent = ui.indent(indent_width) + # Construct lhs and rhs dicts. info = { 'prefix': u'', - 'disk': None, + 'indent': indent, + 'changed': False, 'penalties': penalty_string(match.distance.tracks[track_info]), } lhs = { @@ -393,10 +399,12 @@ def make_track_lengths(item, track_info): # TODO: Is there a better way to determine if a track has changed? if lhs_comp != rhs_comp: # Prefix changed tracks with U+2260: Not Equal To + info['changed'] = True info['prefix'] = ui.colorize('changed', '\u2260 ') return (info, lhs, rhs) elif config['import']['detail']: # Prefix unchanged tracks with * + info['changed'] = False info['prefix'] = '* ' return (info, lhs, {}) @@ -438,19 +446,35 @@ def format_index(track_info): else: return unicode(index) - def format_track(indent, prefix, lhs_width, rhs_width, col_width_l, col_width_r, lhs, rhs): + def format_track(info, lhs_width, rhs_width, col_width_l, col_width_r, lhs, rhs): """docstring for format_track""" - # Print track + # Print track. pad_l = u' ' * (col_width_l - lhs_width) pad_r = u' ' * (col_width_r - rhs_width) - template = "{0} {1} {2}{3}" - lhs_str = template.format( - lhs['track'], lhs['title'], pad_l, lhs['length']) - rhs_str = template.format( - rhs['track'], rhs['title'], pad_r, rhs['length']) - print_(u'{0}{1} ->\n{2}{3}'.format(indent + prefix, lhs_str, indent + ui.indent(len('* ')), rhs_str)) - - def format_track_as_columns(indent, prefix, col_width_l, col_width_r, lhs, rhs): + xhs_template = u'{title} {title} {padding}{length}' + lhs_str = xhs_template.format( + track = lhs['track'], + title = lhs['title'], + padding = pad_l, + length = lhs['length'] + ) + rhs_str = xhs_template.format( + track = rhs['track'], + title = rhs['title'], + padding = pad_r, + length = rhs['length'] + ) + line_template = u'{indent}{prefix}{lhs} ->\n{indent}{padding}{rhs}' + out = line_template.format( + indent = info['indent'], + prefix = info['prefix'], + padding = ui.indent(len('* ')), + lhs = lhs_str, + rhs = rhs_str, + ) + print_(out) + + def format_track_as_columns(info, col_width_l, col_width_r, lhs, rhs): """docstring for format_track_as_columns""" # TODO: Think about how to beautify calc_available_columns_per_line # and ui.split_into_lines, especially with regard to the @@ -501,6 +525,10 @@ def calc_word_wrapping(col_width, xhs): rhs['uncolored'] = {} rhs['uncolored']['title'] = ui.uncolorize(rhs['title']) + # Get indent and prefix. + indent = info['indent'] + prefix = info['prefix'] + # Calculate word wrapping. lhs_lines = calc_word_wrapping(col_width_l, lhs) rhs_lines = calc_word_wrapping(col_width_r, rhs) @@ -585,39 +613,42 @@ def calc_word_wrapping(col_width, xhs): def print_line(info, lhs, rhs): """ """ - l_pre = indent + info['prefix'] - r_pre = indent + ui.indent(len('* ')) - if not rhs: - if info['disk']: - print_(info['disk']) + if 'disk' in info: + # Print disk info. + print_(info['disk']) + elif not info['changed']: + # Print unchanged track. + l_pre = info['indent'] + info['prefix'] + pad_l = ' ' * (max_width_l - lhs['width']) + lhs_str = "{0} {1} {2}{3}".format( + lhs['track'], lhs['title'], pad_l, lhs['length']) + print_(l_pre + lhs_str) + else: + # Print changed track. + if (lhs['width'] > col_width_l) or (rhs['width'] > col_width_r): + layout = \ + config['ui']['import']['albumdiff']['layout'].as_choice({ + 'column': 0, + 'newline': 1, + }) + if layout == 0: + # Word wrapping inside columns. + format_track_as_columns(info, + col_width_l, col_width_r, lhs, rhs) + elif layout == 1: + # Wrap overlong track changes at column border. + format_track(info, lhs['width'], rhs['width'], + max_width_l, max_width_r, lhs, rhs) else: - pad_l = ' ' * (max_width_l - lhs['width']) - lhs_str = "{0} {1} {2}{3}".format( + l_pre = info['indent'] + info['prefix'] + pad_l = ' ' * (col_width_l - lhs['width']) + pad_r = ' ' * (col_width_r - rhs['width']) + template = "{0} {1} {2}{3}" + lhs_str = template.format( lhs['track'], lhs['title'], pad_l, lhs['length']) - print_(l_pre + lhs_str) - elif (lhs['width'] > col_width_l) or (rhs['width'] > col_width_r): - layout = \ - config['ui']['import']['albumdiff']['layout'].as_choice({ - 'column': 0, - 'newline': 1, - }) - if layout == 0: - # Word wrapping inside columns. - format_track_as_columns(indent, info['prefix'], - col_width_l, col_width_r, lhs, rhs) - elif layout == 1: - # Wrap overlong track changes at column border. - format_track(indent, info['prefix'], lhs['width'], rhs['width'], - max_width_l, max_width_r, lhs, rhs) - else: - pad_l = ' ' * (col_width_l - lhs['width']) - pad_r = ' ' * (col_width_r - rhs['width']) - template = "{0} {1} {2}{3}" - lhs_str = template.format( - lhs['track'], lhs['title'], pad_l, lhs['length']) - rhs_str = template.format( - rhs['track'], rhs['title'], pad_r, rhs['length']) - print_(l_pre + u'%s -> %s' % (lhs_str, rhs_str)) + rhs_str = template.format( + rhs['track'], rhs['title'], pad_r, rhs['length']) + print_(l_pre + u'%s -> %s' % (lhs_str, rhs_str)) # Read match detail indentation width from config. detail_indent = get_match_details_indentation() @@ -665,11 +696,10 @@ def print_line(info, lhs, rhs): ### Print lines ### ----------------------------------------------------------------- - joiner_width = len(''.join(['* ', ' -> '])) - tracklist_indent_width = \ - config['ui']['import']['indentation']['match_tracklist'].as_number() - indent = ui.indent(tracklist_indent_width) - col_width = (ui.term_width() - tracklist_indent_width - joiner_width) // 2 + terminal_width = ui.term_width() + joiner_width = len(''.join(['* ', ' -> '])) + indent_width = config['ui']['import']['indentation']['match_tracklist'].as_number() + col_width = (terminal_width - indent_width - joiner_width) // 2 if lines: # Calculate width of left and right column. From bd0b56401211eda5df76bdc9025971fe4fa2ba5e Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Sun, 1 Apr 2018 12:08:54 +0200 Subject: [PATCH 37/45] Remove unused kwargs from colordiff and _colordiff --- beets/ui/__init__.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index d8fb912773..85d8a28704 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -551,13 +551,17 @@ def color_len(colored_text): return len(uncolorize(colored_text)) -def _colordiff(a, b, highlight='text_highlight', - minor_highlight='text_highlight_minor'): +def _colordiff(a, b): """Given two values, return the same pair of strings except with their differences highlighted in the specified color. Strings are highlighted intelligently to show differences; other values are stringified and highlighted in their entirety. """ + # Set highlight colors. + highlight_added = 'text_diff_added' + highlight_removed = 'text_diff_removed' + minor_highlight = 'text_highlight_minor' + if not isinstance(a, basestring) or not isinstance(b, basestring): # Non-strings: use ordinary equality. a = unicode(a) @@ -565,7 +569,7 @@ def _colordiff(a, b, highlight='text_highlight', if a == b: return a, b else: - return colorize(highlight, a), colorize(highlight, b) + return colorize(highlight_removed, a), colorize(highlight_added, b) if isinstance(a, bytes) or isinstance(b, bytes): # A path field. @@ -585,22 +589,22 @@ def _colordiff(a, b, highlight='text_highlight', # Right only. words = re.split('(\s)', b[b_start:b_end]) mapper = lambda w: \ - w if re.match('(\s)', w) else colorize('text_diff_added', w) + w if re.match('(\s)', w) else colorize(highlight_added, w) words_colorized = map(mapper, words) b_out.append(''.join(words_colorized)) elif op == 'delete': # Left only. words = re.split('(\s)', a[a_start:a_end]) mapper = lambda w: \ - w if re.match('(\s)', w) else colorize('text_diff_removed', w) + w if re.match('(\s)', w) else colorize(highlight_removed, w) words_colorized = map(mapper, words) a_out.append(''.join(words_colorized)) elif op == 'replace': # Right and left differ. Colorise with second highlight if # it's just a case change. if a[a_start:a_end].lower() != b[b_start:b_end].lower(): - color_a = 'text_diff_removed' - color_b = 'text_diff_added' + color_a = highlight_removed + color_b = highlight_added else: color_a = minor_highlight color_b = minor_highlight @@ -620,12 +624,12 @@ def _colordiff(a, b, highlight='text_highlight', return u''.join(a_out), u''.join(b_out) -def colordiff(a, b, highlight='text_highlight'): +def colordiff(a, b): """Colorize differences between two values if color is enabled. (Like _colordiff but conditional.) """ if config['ui']['color']: - return _colordiff(a, b, highlight) + return _colordiff(a, b) else: return unicode(a), unicode(b) From de7717efaa1c94f3138e6572e18c9018051d7351 Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Sun, 1 Apr 2018 12:09:33 +0200 Subject: [PATCH 38/45] Refactor mapper functions in _colordiff Create color mapper functions outside of the forloop. --- beets/ui/__init__.py | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 85d8a28704..b8cb8a72b9 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -579,6 +579,10 @@ def _colordiff(a, b): a_out = [] b_out = [] + add_mapper = lambda w: w if re.match('(\s)', w) else colorize(highlight_added, w) + remove_mapper = lambda w: w if re.match('(\s)', w) else colorize(highlight_removed, w) + minor_mapper = lambda w: w if re.match('(\s)', w) else colorize(minor_highlight, w) + matcher = SequenceMatcher(lambda x: False, a, b) for op, a_start, a_end, b_start, b_end in matcher.get_opcodes(): if op == 'equal': @@ -588,34 +592,24 @@ def _colordiff(a, b): elif op == 'insert': # Right only. words = re.split('(\s)', b[b_start:b_end]) - mapper = lambda w: \ - w if re.match('(\s)', w) else colorize(highlight_added, w) - words_colorized = map(mapper, words) + words_colorized = map(add_mapper, words) b_out.append(''.join(words_colorized)) elif op == 'delete': # Left only. words = re.split('(\s)', a[a_start:a_end]) - mapper = lambda w: \ - w if re.match('(\s)', w) else colorize(highlight_removed, w) - words_colorized = map(mapper, words) + words_colorized = map(remove_mapper, words) a_out.append(''.join(words_colorized)) elif op == 'replace': # Right and left differ. Colorise with second highlight if # it's just a case change. - if a[a_start:a_end].lower() != b[b_start:b_end].lower(): - color_a = highlight_removed - color_b = highlight_added - else: - color_a = minor_highlight - color_b = minor_highlight words_a = re.split('(\s)', a[a_start:a_end]) words_b = re.split('(\s)', b[b_start:b_end]) - mapper_a = lambda w: \ - w if re.match('(\s)', w) else colorize(color_a, w) - mapper_b = lambda w: \ - w if re.match('(\s)', w) else colorize(color_b, w) - words_a_colorized = map(mapper_a, words_a) - words_b_colorized = map(mapper_b, words_b) + if a[a_start:a_end].lower() != b[b_start:b_end].lower(): + words_a_colorized = map(remove_mapper, words_a) + words_b_colorized = map(add_mapper, words_b) + else: + words_a_colorized = map(minor_mapper, words_a) + words_b_colorized = map(minor_mapper, words_b) a_out.append(''.join(words_a_colorized)) b_out.append(''.join(words_b_colorized)) else: From ac03469c722cfbff3df2a27fae323c88887e35ed Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Sat, 31 Mar 2018 18:05:35 +0200 Subject: [PATCH 39/45] Improve comments in ui/__init__.py --- beets/ui/__init__.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index b8cb8a72b9..2362429ffe 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -139,13 +139,13 @@ def print_(*strings, **kwargs): def indent(count): - """Indents string with spaces. + """Returns a string with `count` many spaces. """ return u' ' * count def indent_str(count, string): - """Indents string with spaces. + """Returns `string`, indented with `count` many spaces. """ return indent(count) + string @@ -681,13 +681,16 @@ def term_width(): def split_into_lines(string, raw_string, width_tuple): - """Splits string into substrings at whitespace. The first substring has a - length not longer than first_width, the last substring has a length not - longer than last_width, and all other substrings have a length not longer - than middle_width. - If raw_string is defined, raw_string and string contain the same words, but - string contains ANSI codes at word borders. Use raw_string to find - substrings, but return the words of string. + """Splits string into a list of substrings at whitespace. + + `width_tuple` is a 3-tuple of `(first_width, last_width, middle_width)`. + The first substring has a length not longer than `first_width`, the last + substring has a length not longer than `last_width`, and all other + substrings have a length not longer than `middle_width`. + + `raw_string` and `string` are two strings that contain the same words, + but `string` may contain ANSI codes at word borders. Use `raw_string` + to find substrings, but return the words in `string`. """ first_width, middle_width, last_width = width_tuple @@ -708,7 +711,7 @@ def split_into_lines(string, raw_string, width_tuple): #print_('pot_substr_raw: {}'.format(pot_substr_raw)) - # Find out if pot(ential)_substr fits into next substring + # Find out if the pot(ential)_substr fits into the next substring. fits_first = \ (len(result['raw']) == 0 and len(pot_substr_raw) <= first_width) fits_middle = \ From 26c2e1f86231d89772f5342b0afbf96b306887bb Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Sat, 31 Mar 2018 18:06:57 +0200 Subject: [PATCH 40/45] Simplify handling of overlong last lines in `split_into_lines` Instead of sometimes moving the last word in the last line, just always add an empty line as the new last line. --- beets/ui/__init__.py | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 2362429ffe..9915e91bfc 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -724,26 +724,19 @@ def split_into_lines(string, raw_string, width_tuple): result['col'].append(next_substr) next_substr_raw = words_raw[i] next_substr = words[i] - # Assure that last line fits. - if len(next_substr_raw) <= last_width: - result['raw'].append(next_substr_raw) - result['col'].append(next_substr) - else: - words_raw = next_substr_raw.split() - words = next_substr.split() - assert len(words_raw) == len(words) - if len(words_raw) > 1: - last_substr_raw = words_raw.pop() - last_substr = words.pop() - next_substr_raw = ' '.join(words_raw) - next_substr = ' '.join(words) - else: - last_substr_raw = u'' - last_substr = u'' - result['raw'].append(next_substr_raw) - result['col'].append(next_substr) - result['raw'].append(last_substr_raw) - result['col'].append(last_substr) + + # We finished constructing the substrings, but the last substring + # has not yet been added to the result. + result['raw'].append(next_substr_raw) + result['col'].append(next_substr) + + # Also, the length of the last substring was only checked against + # `middle_width`. Append an empty substring as the new last substring if + # the last substring is too long. + if not len(next_substr_raw) <= last_width: + result['raw'].append(u'') + result['col'].append(u'') + return result From 1df0689e26d78fd24d54e641b6954b7aa7b55131 Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Sat, 31 Mar 2018 18:07:17 +0200 Subject: [PATCH 41/45] Whitespace --- beets/ui/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 9915e91bfc..ccdffabd6a 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -700,6 +700,7 @@ def split_into_lines(string, raw_string, width_tuple): result = { 'col': [], 'raw': [] } next_substr_raw = u'' next_substr = u'' + # Iterate over all words. for i in range(len(words_raw)): if i == 0: @@ -709,8 +710,6 @@ def split_into_lines(string, raw_string, width_tuple): pot_substr_raw = ' '.join([next_substr_raw, words_raw[i]]) pot_substr = ' '.join([next_substr, words[i]]) - #print_('pot_substr_raw: {}'.format(pot_substr_raw)) - # Find out if the pot(ential)_substr fits into the next substring. fits_first = \ (len(result['raw']) == 0 and len(pot_substr_raw) <= first_width) From df2d7adc245ee2d1a763318c8149123c6f4ef924 Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Sun, 1 Apr 2018 17:17:12 +0200 Subject: [PATCH 42/45] Modify disambig_string to return colorized string --- beets/ui/commands.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index b55ebf6788..1ce93a1474 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -152,7 +152,7 @@ def disambig_string(info): disambig.append(info.albumdisambig) if disambig: - return u' | '.join(disambig) + return ui.colorize('text_highlight_minor', u' | '.join(disambig)) def dist_colorize(string, dist): @@ -226,7 +226,7 @@ def show_match_header(): # Disambiguation. disambig = disambig_string(match.info) if disambig: - print_(header_indent + ui.colorize('text_highlight_minor', disambig)) + print_(header_indent + disambig) # Data URL. if match.info.data_url: @@ -777,7 +777,7 @@ def show_item_change(item, match): # Disambiguation. disambig = disambig_string(match.info) if disambig: - info.append(ui.colorize('text_highlight_minor', '(%s)' % disambig)) + info.append('(%s)' % disambig) print_(' '.join(info)) @@ -947,7 +947,7 @@ def choose_candidate(candidates, singleton, rec, cur_artist=None, # Disambiguation disambig = disambig_string(match.info) if disambig: - print_(ui.indent(13) + ui.colorize('text_highlight_minor', disambig)) + print_(ui.indent(13) + disambig) # Ask the user for a choice. if singleton: From 9232e1528a8c087c0dd7cba0764b245583848d2c Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Sun, 1 Apr 2018 17:18:38 +0200 Subject: [PATCH 43/45] Improve comments --- beets/ui/commands.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 1ce93a1474..53f573e6ca 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -156,7 +156,8 @@ def disambig_string(info): def dist_colorize(string, dist): - """Formats a string as a colorized similarity string accoring to a distance. + """Formats a string as a colorized similarity string according to + a distance. """ if dist <= config['match']['strong_rec_thresh'].as_number(): string = ui.colorize('text_success', string) @@ -409,7 +410,12 @@ def make_track_lengths(item, track_info): return (info, lhs, {}) def calc_column_width(col_width, max_width_l, max_width_r): - """docstring for calc_column_width + """Calculate column widths for a two-column layout. + `col_width` is the naive width for each column (the total width + divided by 2). + `max_width_l` and `max_width_r` are the maximum width of the + content of each column. + Returns a 2-tuple of the left and right column width. """ if (max_width_l <= col_width) and (max_width_r <= col_width): col_width_l = max_width_l From 275931ecc7fe9f0b8f8c100768239bac3b8d6a20 Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Sun, 1 Apr 2018 17:19:39 +0200 Subject: [PATCH 44/45] Use unicode string when showing empty artist --- 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 53f573e6ca..569c472fac 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -249,7 +249,7 @@ def show_match_details(): detail_indent = get_match_details_indentation() # Artist. - artist_l, artist_r = cur_artist or '', match.info.artist + artist_l, artist_r = cur_artist or u'', match.info.artist if artist_r == VARIOUS_ARTISTS: # Hide artists for VA releases. artist_l, artist_r = u'', u'' From 6de8e5423cbaef5749c93525e54d757186454155 Mon Sep 17 00:00:00 2001 From: Maximilian Merz Date: Sun, 1 Apr 2018 17:29:21 +0200 Subject: [PATCH 45/45] Add ChangeRepresentation class The class should make it easier to handle state when showing a change. - Move show_match_header and show_match_details to the new class. --- beets/ui/commands.py | 105 ++++++++++++++++++++++++++----------------- 1 file changed, 64 insertions(+), 41 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 569c472fac..80a8e84973 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -194,83 +194,104 @@ def penalty_string(distance, limit=None): return ui.colorize('changed', penalty_string) -def show_change(cur_artist, cur_album, match): - """Print out a representation of the changes that will be made if an - album's tags are changed according to `match`, which must be an AlbumMatch - object. +class ChangeRepresentation(object): + """Keeps track of all information needed to generate a (colored) text + representation of the changes that will be made if an album's tags are + changed according to `match`, which must be an AlbumMatch object. """ - def show_match_header(): - """Print out a 'header' identifying the suggested match (album name, - artist name,...) and summarizing the changes that would be made should - the user accept the match. - """ + + cur_artist = None + cur_album = None + match = None + + indent_header = u'' + indent_detail = u'' + + def __init__(self, cur_artist, cur_album, match): + self.cur_artist = cur_artist + self.cur_album = cur_album + self.match = match + # Read match header indentation width from config. match_header_indent_width = \ config['ui']['import']['indentation']['match_header'].as_number() - header_indent = ui.indent(match_header_indent_width) + self.indent_header = ui.indent(match_header_indent_width) + + # Read match detail indentation width from config. + match_detail_indent_width = \ + config['ui']['import']['indentation']['match_details'].as_number() + self.indent_detail = ui.indent(match_detail_indent_width) + def show_match_header(self): + """Print out a 'header' identifying the suggested match (album name, + artist name,...) and summarizing the changes that would be made should + the user accept the match. + """ # Print newline at beginning of change block. print_(u'') # 'Match' line and similarity. - print_(header_indent + u'Match (%s):' % dist_string(match.distance)) + print_(self.indent_header + u'Match (%s):' % dist_string(self.match.distance)) # Artist name and album title. - artist_album_str = u'{0.artist} - {0.album}'.format(match.info) - print_(header_indent + dist_colorize(artist_album_str, match.distance)) + artist_album_str = u'{0.artist} - {0.album}'.format(self.match.info) + print_(self.indent_header + dist_colorize(artist_album_str, self.match.distance)) # Penalties. - penalties = penalty_string(match.distance) + penalties = penalty_string(self.match.distance) if penalties: - print_(header_indent + penalties) + print_(self.indent_header + penalties) # Disambiguation. - disambig = disambig_string(match.info) + disambig = disambig_string(self.match.info) if disambig: - print_(header_indent + disambig) + print_(self.indent_header + disambig) # Data URL. - if match.info.data_url: - url = ui.colorize('text_highlight_minor', '%s' % match.info.data_url) - print_(header_indent + url) - - def get_match_details_indentation(): - """Reads match detail indentation width from config. - """ - match_detail_indent_width = \ - config['ui']['import']['indentation']['match_details'].as_number() - return ui.indent(match_detail_indent_width) + if self.match.info.data_url: + url = ui.colorize('text_highlight_minor', '%s' % self.match.info.data_url) + print_(self.indent_header + url) - def show_match_details(): + def show_match_details(self): """Print out the details of the match, including changes in album name and artist name. """ - # Read match detail indentation width from config. - detail_indent = get_match_details_indentation() - # Artist. - artist_l, artist_r = cur_artist or u'', match.info.artist + artist_l, artist_r = self.cur_artist or u'', self.match.info.artist if artist_r == VARIOUS_ARTISTS: # Hide artists for VA releases. artist_l, artist_r = u'', u'' if artist_l != artist_r: artist_l, artist_r = ui.colordiff(artist_l, artist_r) # Prefix with U+2260: Not Equal To - print_(detail_indent + ui.colorize('changed', u'\u2260'), + print_(self.indent_detail + ui.colorize('changed', u'\u2260'), u'Artist:', artist_l, u'->', artist_r) else: - print_(detail_indent + '*', 'Artist:', artist_r) + print_(self.indent_detail + '*', 'Artist:', artist_r) # Album - album_l, album_r = cur_album or '', match.info.album - if (cur_album != match.info.album \ - and match.info.album != VARIOUS_ARTISTS): + album_l, album_r = self.cur_album or '', self.match.info.album + if (self.cur_album != self.match.info.album \ + and self.match.info.album != VARIOUS_ARTISTS): album_l, album_r = ui.colordiff(album_l, album_r) # Prefix with U+2260: Not Equal To - print_(detail_indent + ui.colorize('changed', u'\u2260'), + print_(self.indent_detail + ui.colorize('changed', u'\u2260'), u'Album:', album_l, u'->', album_r) else: - print_(detail_indent + '*', 'Album:', album_r) + print_(self.indent_detail + '*', 'Album:', album_r) + + +def show_change(cur_artist, cur_album, match): + """Print out a representation of the changes that will be made if an + album's tags are changed according to `match`, which must be an AlbumMatch + object. + """ + def get_match_details_indentation(): + """Reads match detail indentation width from config. + """ + match_detail_indent_width = \ + config['ui']['import']['indentation']['match_details'].as_number() + return ui.indent(match_detail_indent_width) def show_match_tracks(): """Print out the tracks of the match, summarizing changes the match @@ -739,11 +760,13 @@ def print_line(info, lhs, rhs): line += ' (%s)' % ui.human_seconds_short(item.length) print_(ui.colorize('text_warning', line)) + change = ChangeRepresentation(cur_artist=cur_artist, cur_album=cur_album, match=match) + # Print the match header. - show_match_header() + change.show_match_header() # Print the match details. - show_match_details() + change.show_match_details() # Print the match tracks. show_match_tracks()