vimtabdiff/vimtabdiff.py

110 lines
3.4 KiB
Python
Raw Permalink Normal View History

2022-11-30 11:59:52 -05:00
#!/usr/bin/python3
2022-12-10 19:40:11 -05:00
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
2022-12-05 21:01:46 -05:00
import os
2022-12-04 18:22:45 -05:00
import argparse
2022-12-04 19:21:14 -05:00
import itertools
2022-12-05 13:55:05 -05:00
import tempfile
import subprocess
import shlex
from pathlib import Path
2022-12-21 21:29:53 -05:00
from typing import TypeVar
from collections.abc import Iterator, Callable
2022-12-04 18:22:45 -05:00
2022-12-21 21:29:53 -05:00
R = TypeVar('R')
2022-12-09 19:05:14 -05:00
2022-12-21 21:29:53 -05:00
def star(f: Callable[..., R]) -> Callable[[tuple], R]:
2022-12-05 20:13:31 -05:00
""" see https://stackoverflow.com/q/21892989 """
return lambda args: f(*args)
2022-12-09 19:05:14 -05:00
def parse_args() -> argparse.Namespace:
2022-12-09 19:05:14 -05:00
parser = argparse.ArgumentParser(
description="Show diff of files from two directories in vim tabs",
epilog="See https://github.com/balki/vimtabdiff for more info")
parser.add_argument("pathA", type=Path)
parser.add_argument("pathB", type=Path)
2022-12-05 20:13:31 -05:00
parser.add_argument("--vim", help="vim command to run", default="vim")
parser.add_argument(
"--onlydiffs", help="only open files where there is a diff", action="store_true"
)
2022-12-04 18:22:45 -05:00
return parser.parse_args()
2022-12-09 19:05:14 -05:00
def get_dir_info(dirpath: Path | None) -> tuple[list[Path], list[Path]]:
if not dirpath:
2022-12-09 19:05:14 -05:00
return [], []
2022-12-04 18:22:45 -05:00
dirs, files = [], []
for p in dirpath.iterdir():
2022-12-04 18:22:45 -05:00
if p.is_dir():
dirs.append(p)
else:
files.append(p)
return dirs, files
2022-12-09 19:05:14 -05:00
2022-12-21 20:50:43 -05:00
def get_pairs(aPaths: list[Path],
bPaths: list[Path]) -> Iterator[tuple[Path | None, Path | None]]:
aItems = [(item, 'A') for item in aPaths]
bItems = [(item, 'B') for item in bPaths]
2022-12-05 13:55:05 -05:00
abItems = aItems + bItems
2022-12-05 20:13:31 -05:00
abItems.sort(key=star(lambda item, tag: (item.name, tag)))
2022-12-09 19:05:14 -05:00
for _, items in itertools.groupby(abItems,
key=star(lambda item, _: item.name)):
match list(items):
case [(aItem, _), (bItem, _)]:
yield aItem, bItem
case [(item, 'A'),]:
2022-12-04 19:21:14 -05:00
yield item, None
case [(item, 'B'),]:
2022-12-04 19:21:14 -05:00
yield None, item
2022-12-09 19:05:14 -05:00
2022-12-21 20:50:43 -05:00
def get_file_pairs(
a: Path | None,
b: Path | None) -> Iterator[tuple[Path | None, Path | None]]:
2022-12-05 13:55:05 -05:00
aDirs, aFiles = get_dir_info(a)
bDirs, bFiles = get_dir_info(b)
2022-12-04 19:36:51 -05:00
yield from get_pairs(aFiles, bFiles)
for aDir, bDir in get_pairs(aDirs, bDirs):
2022-12-04 19:21:14 -05:00
yield from get_file_pairs(aDir, bDir)
2022-11-30 11:59:52 -05:00
2022-12-09 19:05:14 -05:00
def main() -> None:
2022-12-04 18:22:45 -05:00
args = parse_args()
2022-12-05 13:55:05 -05:00
vimCmdFile = tempfile.NamedTemporaryFile(mode='w', delete=False)
with vimCmdFile:
cmds = f"""
let s:spr = &splitright
set splitright
"""
print(cmds, file=vimCmdFile)
2022-12-05 13:55:05 -05:00
for a, b in get_file_pairs(args.pathA, args.pathB):
2022-12-05 21:01:46 -05:00
aPath = a.resolve() if a else os.devnull
bPath = b.resolve() if b else os.devnull
if (
args.onlydiffs
and a and b
and open(aPath, mode="rb").read() == open(bPath, mode="rb").read()
):
continue
2022-12-21 20:50:43 -05:00
print(f"tabedit {aPath} | vsp {bPath}", file=vimCmdFile)
2022-12-07 18:04:27 -05:00
cmds = f"""
let &splitright = s:spr
tabdo windo :1
2022-12-21 20:50:43 -05:00
tabdo windo diffthis
tabdo windo diffupdate
2022-12-07 18:04:27 -05:00
tabfirst | tabclose
call delete("{vimCmdFile.name}")
"""
print(cmds, file=vimCmdFile)
subprocess.run(shlex.split(args.vim) + ["-S", vimCmdFile.name])
2022-11-30 11:59:52 -05:00
2022-12-09 19:05:14 -05:00
2022-11-30 11:59:52 -05:00
if __name__ == '__main__':
main()