Skip to content

Commit

Permalink
Adding shelephant diff (#191)
Browse files Browse the repository at this point in the history
  • Loading branch information
tdegeus authored Nov 6, 2023
1 parent cafd2bc commit 41f61b4
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 0 deletions.
3 changes: 3 additions & 0 deletions shelephant/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,7 @@ class MyFmt(
"mv",
"rm",
"pwd",
"diff",
"gitignore",
"add",
"remove",
Expand Down Expand Up @@ -841,6 +842,8 @@ def _shelephant_main():
dataset.rm(sys.argv[2:])
elif args.command == "pwd":
dataset.pwd(sys.argv[2:])
elif args.command == "diff":
dataset.diff(sys.argv[2:])
elif args.command == "gitignore":
dataset.gitignore(sys.argv[2:])
elif args.command == "add":
Expand Down
56 changes: 56 additions & 0 deletions shelephant/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -1648,6 +1648,62 @@ def pwd(args: list[str]):
print(os.path.relpath(root / post, os.getcwd()))


def _diff_parser():
"""
Return parser for :py:func:`shelephant diff`.
"""

desc = textwrap.dedent(
"""
Show differences between two storage locations.
"""
)

class MyFmt(
argparse.RawDescriptionHelpFormatter,
argparse.ArgumentDefaultsHelpFormatter,
argparse.MetavarTypeHelpFormatter,
):
pass

parser = argparse.ArgumentParser(formatter_class=MyFmt, description=desc)

parser.add_argument("--version", action="version", version=version)
parser.add_argument("--colors", type=str, default="dark", help="Color scheme [none, dark].")
parser.add_argument("--pop", type=str, action="append", help='Pop direction (e.g. "==").')
parser.add_argument("source", type=str, help="Storage location.")
parser.add_argument("dest", type=str, help="Storage location.")
return parser


def diff(args: list[str]):
"""
Command-line tool, see ``--help``.
:param args: Command-line arguments (should be all strings).
"""

parser = _diff_parser()
args = parser.parse_args(args)
sdir = _search_upwards_dir(".shelephant")
assert sdir is not None, "Not in a shelephant dataset"

with search.cwd(sdir):
storage = yaml.read(sdir / "storage.yaml")
assert args.source in storage, f"Unknown storage location {args.source}"
assert args.dest in storage, f"Unknown storage location {args.dest}"
source = Location.from_yaml(f"storage/{args.source}.yaml")
dest = Location.from_yaml(f"storage/{args.dest}.yaml")

status = source.diff(dest)
if args.pop is not None:
for i in args.pop:
status.pop(i)
for key in status:
status[key] = sorted(status[key])
output.diff(status, colors=args.colors)


def _status_parser():
"""
Return parser for :py:func:`shelephant status`.
Expand Down
96 changes: 96 additions & 0 deletions shelephant/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,99 @@ def copyplan(
return sio.getvalue()

autoprint(sio.getvalue())


def diff(
status: dict[list[str]],
colors: str = "none",
display: bool = True,
max_align: int = 80,
) -> str:
"""
Print copy plan.
:param status:
Dictionary of status. E.g.::
{
'==' : ['file4'],
'?=' : [],
'!=' : ['file3'],
'->' : ['file1', 'file2'],
'<-' : [],
}
:param colors: Color theme name, see :py:func:`theme`.
:param display: Display output (``False``: return as string).
:param max_align: Maximum width of the first column.
:return: Output string (if ``display=False``).
"""
color = _theme(colors.lower())
sio = io.StringIO()

skip = status.pop("==", [])
right = status.pop("->", [])
left = status.pop("<-", [])
ne = status.pop("!=", [])
na = status.pop("?=", [])

if len(ne) + len(na) + len(left) + len(right) + len(skip) == 0:
return

width = max(len(file) for file in ne + na + left + right + skip)
width = min(width, max_align)

for file in ne:
print(
"{:s} {:s} {:s}".format(
_format(file, width=width, color=color["overwrite"]),
_format("!=", color=color["bright"]),
_format(file, color=color["overwrite"]),
),
file=sio,
)

for file in na:
print(
"{:s} {:s} {:s}".format(
_format(file, width=width, color=color["overwrite"]),
_format("?=", color=color["bright"]),
_format(file, color=color["overwrite"]),
),
file=sio,
)

for file in left:
print(
"{:s} {:s} {:s}".format(
_format(file, width=width, color=color["new"]),
_format("<-", color=color["bright"]),
_format(file, color=color["bright"]),
),
file=sio,
)

for file in right:
print(
"{:s} {:s} {:s}".format(
_format(file, width=width, color=color["bright"]),
_format("->", color=color["bright"]),
_format(file, color=color["new"]),
),
file=sio,
)

for file in skip:
print(
"{:s} {:s} {:s}".format(
_format(file, width=width, color=color["skip"]),
_format("==", color=color["skip"]),
_format(file, color=color["skip"]),
),
file=sio,
)

if not display:
return sio.getvalue()

autoprint(sio.getvalue())
14 changes: 14 additions & 0 deletions tests/test_dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,20 @@ def test_basic(self):
ret = _plain(sio.getvalue())[1:]
self.assertEqual(ret, expect)

with cwd(dataset), contextlib.redirect_stdout(io.StringIO()) as sio:
shelephant.dataset.diff(["--colors", "none", "source1", "source2"])

expect = [
"e.txt <- e.txt",
"f.txt <- f.txt",
"c.txt -> c.txt",
"d.txt -> d.txt",
"a.txt == a.txt",
"b.txt == b.txt",
]
ret = _plain(sio.getvalue())
self.assertEqual(ret, expect)

with cwd(dataset):
for f in ["a.txt", "b.txt", "c.txt", "d.txt"]:
self.assertEqual(pathlib.Path(f).readlink().parent.name, "source1")
Expand Down

0 comments on commit 41f61b4

Please sign in to comment.