Skip to content

Commit

Permalink
Merge pull request #5 from m-matelski/gui_tkinter
Browse files Browse the repository at this point in the history
Gui tkinter
  • Loading branch information
m-matelski committed Mar 15, 2022
2 parents 5197b8e + b10d8bd commit 1267541
Show file tree
Hide file tree
Showing 28 changed files with 1,065 additions and 284 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ __pycache__
comp_examples
venv
build
dist
dist
mdiff.egg-info
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
### 0.10.0
* Added GUI widget to present diff result
* Added standalone GUI application.
* Added `case-sensitive` parameter to diff.

### 0.0.5
* Updated readme.
* Simplified some argument names and changed docstrings.
Expand Down
30 changes: 22 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ For plain python package (no additional dependencies):
pip install mdiff
```

For additional CLI tool functionalities (uses external packages such as [colorama](https://github.com/tartley/colorama),
For additional CLI tool and GUI functionalities (uses external packages such as [colorama](https://github.com/tartley/colorama),
or [Typer](https://github.com/tiangolo/typer)):
```console
pip install mdiff[cli]
pip install mdiff[tools]
```

## Usage
Expand Down Expand Up @@ -139,7 +139,7 @@ replace a_lines[4:5] --> b_lines[4:5] ['line5'] --> ['line6']
Indented tags shows in-line differences, in this case `line5` and `line6` strings have the only difference at last character.
## CLI Tool

mdiff also provides CLI tool (available only if installed using `pip install mdiff[cli]`). For more information
mdiff also provides CLI tool (available only if installed using `pip install mdiff[tools]`). For more information
type `mdiff --help`

```console
Expand Down Expand Up @@ -178,10 +178,15 @@ Options:
--cutoff FLOAT RANGE Line similarity ratio cutoff. If value
exceeded then finds in-line differences in
similar lines. [default: 0.75; 0.0<=x<=1.0]
--char-mode [utf8|ascii] Character set used when printing diff
result. [default: utf8]
--color-mode [fore|back] Color mode used when printing diff result.
[default: fore]
--gui / --no-gui Open GUI window with diff result. [default:
no-gui]
--case-sensitive / --no-case-sensitive
Whether diff is going to be case sensitive.
[default: case-sensitive]
--char-mode [utf8|ascii] Terminal character set used when printing
diff result. [default: utf8]
--color-mode [fore|back] Terminal color mode used when printing diff
result. [default: fore]
--install-completion [bash|zsh|fish|powershell|pwsh]
Install completion for the specified shell.
--show-completion [bash|zsh|fish|powershell|pwsh]
Expand All @@ -191,4 +196,13 @@ Options:
```
### Example
Sample output for `mdiff a.txt b.txt` command:
![](https://github.com/m-matelski/mdiff/raw/master/resources/readme/mdiff_cli1.png)

![](https://github.com/m-matelski/mdiff/raw/master/resources/readme/mdiff_cli1.png)

Showing result in GUI `mdiff a.txt b.txt --gui`:

![](https://github.com/m-matelski/mdiff/raw/master/resources/readme/mdiff_gui1.png)


## GUI
Use `mdiff-gui` command to launch GUI application.
1 change: 1 addition & 0 deletions VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.1.0
44 changes: 15 additions & 29 deletions mdiff/cli.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,13 @@
from enum import Enum
from pathlib import Path

import typer

from mdiff.seqmatch.utils import seq_matcher_factory
from mdiff.text_diff import diff_lines_with_similarities
import mdiff.visualisation.terminal as cli_vis
from mdiff.utils import read_file
from mdiff.differ import ConsoleTextDiffer, TkinterGuiDiffer
from mdiff.utils import read_file, StringEnumChoice

sm_valid_names = ('standard', 'heckel', 'displacement')


class StringEnumChoice(str, Enum):
"""
Base class for typer choice enum strings. Predefined __str__ method fixes the bug where
default showed StringEnumChoice.value instead of value
"""

def __str__(self):
return self.value


class SequenceMatcherName(StringEnumChoice):
STANDARD = 'standard'
HECKEL = 'heckel'
Expand Down Expand Up @@ -49,12 +36,14 @@ def cli_diff(source_file: Path = typer.Argument(..., help="Source file path to c
0.75, min=0.0, max=1.0,
help='Line similarity ratio cutoff. If value exceeded then finds in-line differences in similar lines.'
),
gui: bool = typer.Option(False, help='Open GUI window with diff result.'),
case_sensitive: bool = typer.Option(True, help='Whether diff is going to be case sensitive.'),
char_mode: CharacterMode = typer.Option(
CharacterMode.UTF8,
help='Character set used when printing diff result.'),
help='Terminal character set used when printing diff result.'),
color_mode: ColorMode = typer.Option(
ColorMode.FORE,
help='Color mode used when printing diff result.'
help='Terminal color mode used when printing diff result.'
)):
"""
Reads 2 files from provided paths, compares their content and prints diff.
Expand All @@ -72,18 +61,15 @@ def cli_diff(source_file: Path = typer.Argument(..., help="Source file path to c
"""
source = read_file(source_file)
target = read_file(target_file)
line_sm_instance = seq_matcher_factory(line_sm.value)()
inline_sm_instance = seq_matcher_factory(inline_sm.value)()
console_characters = cli_vis.get_console_characters(char_mode.value)
console_colors = cli_vis.get_console_colors(color_mode.value)

a_lines, b_lines, opcodes = diff_lines_with_similarities(
a=source, b=target, cutoff=cutoff, line_sm=line_sm_instance, inline_sm=inline_sm_instance)

printer = cli_vis.LineDiffConsolePrinter(a=a_lines, b=b_lines, seq=opcodes,
characters=console_characters,
colors=console_colors, line_margin=3, equal_context=-1)
printer.print()
if not gui:
differ = ConsoleTextDiffer(a=source, b=target, line_sm=line_sm, inline_sm=inline_sm, cutoff=cutoff,
color_mode=color_mode.value, character_mode=char_mode.value,
case_sensitive=case_sensitive)
differ.run()
else:
differ = TkinterGuiDiffer(a=source, b=target, line_sm=line_sm, inline_sm=inline_sm, cutoff=cutoff,
case_sensitive=case_sensitive)
differ.run()


def main():
Expand Down
62 changes: 62 additions & 0 deletions mdiff/differ.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from abc import ABC, abstractmethod
import tkinter as tk

from mdiff import diff_lines_with_similarities
from mdiff.seqmatch.utils import SequenceMatcherName, seq_matcher_factory

import mdiff.visualisation.terminal as cli_vis
from mdiff.visualisation.gui_tkinter.diff_result import DiffResult, DiffResultWindowBuilder


class TextDiffer(ABC):
def __init__(self, a: str, b: str, line_sm: SequenceMatcherName, inline_sm: SequenceMatcherName, cutoff: float,
case_sensitive: bool):
self.a = a
self.b = b
self.line_sm = line_sm
self.inline_sm = inline_sm
self.cutoff = cutoff
self.case_sensitive = case_sensitive
if not 0.0 <= cutoff <= 1.0:
raise ValueError('cutoff must be in range: 0.0 <= cutoff <= 1.0')

self.line_sm_instance = seq_matcher_factory(SequenceMatcherName(line_sm))()
self.inline_sm_instance = seq_matcher_factory(SequenceMatcherName(inline_sm))()

@abstractmethod
def run(self):
pass


class ConsoleTextDiffer(TextDiffer):
def __init__(self, a: str, b: str, line_sm: SequenceMatcherName, inline_sm: SequenceMatcherName, cutoff: float,
case_sensitive: bool, color_mode: str, character_mode: str):
super().__init__(a, b, line_sm, inline_sm, cutoff, case_sensitive)
self.color_mode = color_mode
self.character_mode = character_mode
self.console_characters = cli_vis.get_console_characters(character_mode)
self.console_colors = cli_vis.get_console_colors(color_mode)

def run(self):
a_lines, b_lines, opcodes = diff_lines_with_similarities(
a=self.a, b=self.b, cutoff=self.cutoff, line_sm=self.line_sm_instance, inline_sm=self.inline_sm_instance,
keepends=False, case_sensitive=self.case_sensitive)

printer = cli_vis.LineDiffConsolePrinter(a=a_lines, b=b_lines, seq=opcodes,
characters=self.console_characters,
colors=self.console_colors, line_margin=3, equal_context=-1)
printer.print()


class TkinterGuiDiffer(TextDiffer):

def run(self):
root = tk.Tk()
root.title('Diff Result')

diff_result = DiffResult(root)
window = DiffResultWindowBuilder(root, diff_result)
diff_result.set_diff_params(a=self.a, b=self.b, line_sm_name=self.line_sm, inline_sm_name=self.inline_sm,
cutoff=self.cutoff, case_sensitive=self.case_sensitive)
diff_result.generate_diff()
root.mainloop()
7 changes: 4 additions & 3 deletions mdiff/seqmatch/utils.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from difflib import SequenceMatcher
from typing import Literal, Type
from enum import Enum
from typing import Type

from mdiff.seqmatch.heckel import HeckelSequenceMatcher, DisplacementSequenceMatcher
from mdiff.utils import SequenceMatcherBase


class SequenceMatcherName:
class SequenceMatcherName(str, Enum):
STANDARD = 'standard'
HECKEL = 'heckel'
DISPLACEMENT = 'displacement'
Expand All @@ -18,7 +19,7 @@ class SequenceMatcherName:
}


def seq_matcher_factory(seq_matcher_type: str) -> Type[SequenceMatcherBase]:
def seq_matcher_factory(seq_matcher_type: SequenceMatcherName) -> Type[SequenceMatcherBase]:
values = SequenceMatcherName.__dict__.values()
if seq_matcher_type not in values:
raise ValueError(f'seq_matcher_type must be in: {values}')
Expand Down
25 changes: 17 additions & 8 deletions mdiff/text_diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ def find_best_similar_match(i1: int, i2: int, j1: int, j2: int, a: Sequence, b:
for i in range(i1, i2):
sm.set_seq1(a[i])
for j in range(j1, j2):
if a[i] == b[j]:
continue
sm.set_seq2(b[j])
if sm.real_quick_ratio() > best_ratio and sm.quick_ratio() > best_ratio and sm.ratio() > best_ratio:
best_i = i
Expand Down Expand Up @@ -69,9 +71,7 @@ def extract_replace_similarities(tag: str, i1: int, i2: int, j1: int, j2: int, a
sm = SequenceMatcher()

match_i, match_j, match_ratio = find_best_similar_match(i1, i2, j1, j2, a, b)
if match_ratio >= 1.0:
yield CompositeOpCode('equal', i1, i2, j1, j2)
elif match_ratio > cutoff:
if match_ratio > cutoff:
# left
yield from extract_replace_similarities(tag, i1, match_i, j1, match_j, a, b, cutoff, sm)

Expand Down Expand Up @@ -124,7 +124,9 @@ def extract_similarities(opcodes: OpCodesType, a: Sequence, b: Sequence, cutoff:

def diff_lines_with_similarities(a: str, b: str, cutoff=0.75,
line_sm: SequenceMatcherBase = None,
inline_sm: SequenceMatcherBase = None) \
inline_sm: SequenceMatcherBase = None,
keepends=False,
case_sensitive=True) \
-> Tuple[List[str], List[str], List[CompositeOpCode]]:
"""
Takes input strings "a" and "b", splits them by newline characters and generates line diff opcodes.
Expand All @@ -139,6 +141,7 @@ def diff_lines_with_similarities(a: str, b: str, cutoff=0.75,
if sub opcodes for similar lines should be generated.
:param line_sm: SequenceMatcher object used to generate diff tags between input texts lines.
:param inline_sm: SequenceMatcher object used to generate diff tags between characters in similar lines.
:param keepends: whether to keep newline characters when splitting input sequences.
:return: (a_lines, b_lines, opcodes) where:
a_lines: is "a" input text split by newline characters.
Expand Down Expand Up @@ -166,9 +169,15 @@ def diff_lines_with_similarities(a: str, b: str, cutoff=0.75,
if inline_sm is None:
inline_sm = SequenceMatcher()

a_lines = a.splitlines(keepends=False)
b_lines = b.splitlines(keepends=False)
line_sm.set_seqs(a_lines, b_lines)
a_lines = a.splitlines(keepends=keepends)
b_lines = b.splitlines(keepends=keepends)
if case_sensitive:
sm_a_lines = a_lines
sm_b_lines = b_lines
else:
sm_a_lines = [i.lower() for i in a_lines]
sm_b_lines = [i.lower() for i in b_lines]
line_sm.set_seqs(sm_a_lines, sm_b_lines)
line_opcodes = line_sm.get_opcodes()
line_opcodes_with_similarities = extract_similarities(line_opcodes, a_lines, b_lines, cutoff, inline_sm)
line_opcodes_with_similarities = extract_similarities(line_opcodes, sm_a_lines, sm_b_lines, cutoff, inline_sm)
return a_lines, b_lines, list(line_opcodes_with_similarities)
Loading

0 comments on commit 1267541

Please sign in to comment.