Skip to content

Commit

Permalink
accomodate non-Meld diff GUI
Browse files Browse the repository at this point in the history
  • Loading branch information
scivision committed May 20, 2020
1 parent 3e9b7a3 commit a95dd84
Show file tree
Hide file tree
Showing 11 changed files with 117 additions and 119 deletions.
1 change: 0 additions & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
[run]
cover_pylib = false
omit =
/home/travis/virtualenv/*
*/site-packages/*
*/bin/*

Expand Down
20 changes: 11 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
# Meld Utilities
# Graphical recursive file comparison

[![DOI](https://zenodo.org/badge/186922933.svg)](https://zenodo.org/badge/latestdoi/186922933)
[![Actions Status](https://github.com/scivision/meldutils/workflows/ci/badge.svg)](https://github.com/scivision/meldutils/actions)

[![pypi versions](https://img.shields.io/pypi/pyversions/meldutils.svg)](https://pypi.python.org/pypi/meldutils)
[![PyPi Download stats](http://pepy.tech/badge/meldutils)](http://pepy.tech/project/meldutils)

Python scripts using
Using
[Meld](https://meldmerge.org)
to accomplish tasks useful for managing large numbers of repos / projects, particularly for templates.
It works on any OS Meld works on (Linux, MacOS, Windows).
or
[Visual Studio Code](https://code.visualstudio.com/)
to accomplish file differencing.
Useful for managing large numbers of repos / projects, particularly for templates.

## Scripts

```sh
meld_all project/myfile.f90 ~/code
```

That terminal command invokes `meld` between `project/myfile.f90` and every other file with the same name found recursively under `~/code`.
graphically compares `project/myfile.f90` with every other file of the same name recursively under `~/code`.

### Usage

Particularly on Windows, you may get Meld brought up and you don't see any difference.
This is often because one file as a trailing \n and the other file does not, or the other file has \r\n.
Particularly on Windows, the GUI may be invoked, but you don't see any difference.
This is often because only one of the two files as a trailing `\n` or `\r\n`.
Meld won't show any difference, even with all text filters off.
Because of how Python `filecmp.cmp` works, there isn't a blazing fast simple solution to this besides using str.replace, which I didn't want to do.
Because of how Python `filecmp.cmp` works, there isn't a blazing fast simple solution to this.
A possibly slow solution would be using str.replace.

So just realize it's OK, close Meld when it shows no difference and happy comparing!

Expand Down
2 changes: 1 addition & 1 deletion mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ ignore_missing_imports = True
strict_optional = False
allow_redefinition = True
show_error_context = False
show_column_numbers = True
show_column_numbers = True
8 changes: 4 additions & 4 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
[metadata]
name = meldutils
version = 1.2.1
version = 2.0.0
author = Michael Hirsch, Ph.D.
author_email = scivision@users.noreply.github.com
description = Python utilities for Meld, good for managing templates across lots of repos / projects
description = Graphical recursive file diffing (Meld, VS Code), good for managing templates across lots of repos / projects
url = https://github.com/scivision/meldutils
keywords =
meld
merge
git
diff
classifiers =
Development Status :: 5 - Production/Stable
Environment :: Console
Expand All @@ -20,6 +21,7 @@ classifiers =
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Topic :: Software Development :: Version Control
Topic :: Text Processing
Topic :: Utilities
license_files =
LICENSE.txt
Expand All @@ -46,5 +48,3 @@ tests =
lint =
flake8
mypy
linguist =
ghlinguist
4 changes: 0 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
#!/usr/bin/env python
import setuptools
import sys

if sys.version_info < (3, 6):
raise SystemExit("Python >= 3.6 required")

setuptools.setup()
2 changes: 1 addition & 1 deletion src/meldutils/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from .base import files_to_meld, meld_files
from .base import files_to_diff, diff_gui
21 changes: 6 additions & 15 deletions src/meldutils/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,20 @@ def main():
p = ArgumentParser()
p.add_argument("ref", help="filename to compare against")
p.add_argument("root", help="top-level directory to search under")
p.add_argument("-l", "--language", help="language to template")
p.add_argument("-exe", help="program to compare with", default="meld")
p.add_argument("-s", "--strict", help="compare only with first language match", action="store_true")
p.add_argument("-exe", help="program to compare with")
p.add_argument("-n", "--dryrun", help="just report files that are different", action="store_true")
p = p.parse_args()

if p.dryrun:
level = logging.INFO
else:
level = logging.WARNING
level = logging.INFO if p.dryrun else None
logging.basicConfig(format="%(message)s", level=level)

root = Path(p.root).expanduser()
if not root.is_dir():
raise SystemExit(f"{root} is not a directory")
ref = Path(p.ref).expanduser().resolve()

files = mu.files_to_meld(root, p.ref, p.language, strict=p.strict)

for file in files:
for file in mu.files_to_diff(p.root, ref):
if p.dryrun:
print(f"{file} != {p.ref}")
print(file, "!=", ref)
else:
mu.meld_files(p.ref, file, p.exe)
mu.diff_gui(p.ref, file, p.exe)


if __name__ == "__main__":
Expand Down
67 changes: 37 additions & 30 deletions src/meldutils/base.py
Original file line number Diff line number Diff line change
@@ -1,59 +1,66 @@
import typing
import typing as T
from pathlib import Path
import shutil
import subprocess
import logging
import filecmp
import os

try:
import ghlinguist as ghl
except (ImportError, FileNotFoundError):
ghl = None
__all__ = ["files_to_diff", "diff_gui"]


def files_to_meld(root: Path, ref: Path, language: str = None, strict: bool = False) -> typing.Iterator[Path]:

si = 1 if strict else 2
def files_to_diff(root: Path, ref: Path) -> T.Iterator[Path]:
"""
Yield filenames that match criteria (same filename but different contents).
"""

ref = Path(ref).expanduser()
ref = Path(ref).expanduser().resolve()
if not ref.is_file():
raise FileNotFoundError(f"specify a reference file, not a directory {ref}")

root = Path(root).expanduser()
root = Path(root).expanduser().resolve()
if not root.is_dir():
raise NotADirectoryError(f"{root} is not a directory")

for new in root.rglob(ref.name):
if new.samefile(ref):
continue

if filecmp.cmp(new, ref, shallow=False): # type: ignore # mypy .pyi needs updating
if filecmp.cmp(new, ref, shallow=False): # type: ignore # MyPy .pyi incorrect
logging.info(f"SAME: {new.parent}")
continue

if language and ghl is not None:
langlist = ghl.linguist(new.parent)
if langlist is None:
logging.info(f"SKIP: {new.parent}")
continue

thislangs = [lang[0] for lang in langlist[:si]]
if language not in thislangs:
logging.info(f"SKIP: {new.parent} {thislangs}")
continue

yield new


def meld_files(ref: Path, new: Path, rexe: str):
def diff_gui(ref: Path, new: Path, exe: str = None) -> None:
"""
run file comparison program (often meld) on file pair
run file comparison GUI on file pair
Not using check_call due to spurious errors
"""

exe = shutil.which(rexe)
diff_cli = "FC.exe" if os.name == "nt" else "diff"

if not exe:
for e in ("meld", "code", diff_cli):
exe = shutil.which(e)
if exe:
break

exe = shutil.which(exe)
if not exe:
raise FileNotFoundError(f"{rexe} not found. Try -n option to just see which files differ.")
# Not using check_call due to spurious errors
new = Path(new).expanduser()
ref = Path(ref).expanduser()
subprocess.run([exe, str(ref), str(new)])
raise FileNotFoundError("diff GUI not found. Try -n option to just see which files differ.")

new = Path(new).expanduser().resolve()
ref = Path(ref).expanduser().resolve()

if "code" in Path(exe).name.lower():
cmd = [exe, "--wait", "--diff", str(ref), str(new)]
else:
# assuming just the plain executable name without options
# this is how "meld" works
cmd = [exe, str(ref), str(new)]

logging.info(cmd)
subprocess.run(cmd)
18 changes: 18 additions & 0 deletions src/meldutils/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import pytest
from pathlib import Path


@pytest.fixture
def gen_file(tmp_path):
f1 = tmp_path / "a/hi.txt"
f2 = tmp_path / "b/hi.txt"

make_file(f1)
make_file(f2)

return f1, f2


def make_file(path: Path):
path.parent.mkdir(exist_ok=True, parents=True)
path.write_text("hello")
39 changes: 39 additions & 0 deletions src/meldutils/tests/test_all.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env python
import pytest
import os

import meldutils as mu


def test_find(gen_file):
f1, f2 = gen_file

files = list(mu.files_to_diff(f1.parents[1], f1))

assert len(files) == 0

# add a whitespace to one file to make the file slightly different than the other file
with f2.open("a") as f:
f.write(" ")

files = list(mu.files_to_diff(f1.parents[1], f1))

assert len(files) == 1


def test_diff(gen_file):
"""
show output of this test with
pytest -s
"""
f1, f2 = gen_file
f2.write_text("hi")

# this needs to be FC.exe in case run from PowerShell
diff_cli = "FC.exe" if os.name == "nt" else "diff"

mu.diff_gui(f1, f2, diff_cli)


if __name__ == "__main__":
pytest.main([__file__])
54 changes: 0 additions & 54 deletions src/meldutils/tests/test_meld.py

This file was deleted.

0 comments on commit a95dd84

Please sign in to comment.