Skip to content

Instantly share code, notes, and snippets.

@ines
Created August 1, 2019 12:19
Show Gist options
  • Save ines/04b47597eb9d011ade5e77a068389521 to your computer and use it in GitHub Desktop.
Save ines/04b47597eb9d011ade5e77a068389521 to your computer and use it in GitHub Desktop.
Print colored visual diff in Python
import difflib
import wasabi
def diff_strings(a, b):
output = []
matcher = difflib.SequenceMatcher(None, a, b)
for opcode, a0, a1, b0, b1 in matcher.get_opcodes():
if opcode == "equal":
output.append(a[a0:a1])
elif opcode == "insert":
output.append(color(b[b0:b1], fg=16, bg="green"))
elif opcode == "delete":
output.append(color(a[a0:a1], fg=16, bg="red"))
elif opcode == "replace":
output.append(color(b[b0:b1], fg=16, bg="green"))
output.append(color(a[a0:a1], fg=16, bg="red"))
return "".join(output)
print(diff_strings("helloo world", "hello world!"))
@versae
Copy link

versae commented Aug 1, 2019

Nice! Also, I think the import should be from wasabi import color.

@PFython
Copy link

PFython commented Feb 9, 2022

Thanks for this Ines. If it's of interest, here's my remake which avoids the wasabi dependency and allows you to compare any two values in a list of strings:

import difflib

def diff(string_list, index_a=0, index_b=None, print_only=True):
    """
    Print or return a colour-coded diff of two items in a list of strings.
    Default: Compare first and last strings; print the output; return None.
    """
    index_b = index_b or len(string_list) -1
    green = '\x1b[38;5;16;48;5;2m'
    red = '\x1b[38;5;16;48;5;1m'
    end = '\x1b[0m'
    output = []
    string_a = string_list[index_a]
    string_b = string_list[index_b]
    matcher = difflib.SequenceMatcher(None, string_a, string_b)
    for opcode, a0, a1, b0, b1 in matcher.get_opcodes():
        if opcode == "equal":
            output += [string_a[a0:a1]]
        elif opcode == "insert":
            output += [green + string_b[b0:b1] + end]
        elif opcode == "delete":
            output += [red + string_a[a0:a1] + end]
        elif opcode == "replace":
            output += [green + string_b[b0:b1] + end]
            output += [red + string_a[a0:a1] + end]
    output = "".join(output)
    if not print_only:
        return output
    print(f"\n{output}\n")

@district10
Copy link

district10 commented Feb 23, 2022

Same API as original post:

import difflib

def diff_strings(a: str, b: str, *, use_loguru_colors: bool = False) -> str:
    output = []
    matcher = difflib.SequenceMatcher(None, a, b)
    if use_loguru_colors:
        green = '<GREEN><black>'
        red = '<RED><black>'
        endgreen = '</black></GREEN>'
        endred = '</black></RED>'
    else:
        green = '\x1b[38;5;16;48;5;2m'
        red = '\x1b[38;5;16;48;5;1m'
        endgreen = '\x1b[0m'
        endred = '\x1b[0m'

    for opcode, a0, a1, b0, b1 in matcher.get_opcodes():
        if opcode == 'equal':
            output.append(a[a0:a1])
        elif opcode == 'insert':
            output.append(f'{green}{b[b0:b1]}{endgreen}')
        elif opcode == 'delete':
            output.append(f'{red}{a[a0:a1]}{endred}')
        elif opcode == 'replace':
            output.append(f'{green}{b[b0:b1]}{endgreen}')
            output.append(f'{red}{a[a0:a1]}{endred}')
    return ''.join(output)

print(diff_strings('helloo world', 'hello world!'))

from loguru import logger
logger.opt(raw=True, colors=True).info(diff_strings('helloo world', 'hello world!', use_loguru_colors=True))

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment