aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.editorconfig10
-rw-r--r--patchtree/cli.py9
-rw-r--r--patchtree/context.py89
3 files changed, 81 insertions, 27 deletions
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..a5ddde0
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,10 @@
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = false
+max_line_length = 80
+
+[*.py]
+indent_size = 4
+indent_style = space
diff --git a/patchtree/cli.py b/patchtree/cli.py
index 5ec8213..7b1a108 100644
--- a/patchtree/cli.py
+++ b/patchtree/cli.py
@@ -27,6 +27,12 @@ def parse_arguments(config: Config) -> Context:
help="output file (stdout by default)",
type=str,
)
+ parser.add_argument(
+ "-i",
+ "--in-place",
+ help="patch target in-place",
+ action="store_true",
+ )
parser.add_argument("target", help="target directory or archive")
parser.add_argument("patch", help="patch input glob(s)", nargs="+")
@@ -61,9 +67,6 @@ def main():
patch = config.patch(config, file)
patch.write(context)
- context.output.flush()
- context.output.close()
-
return 0
diff --git a/patchtree/context.py b/patchtree/context.py
index 8fa7abd..a91fb36 100644
--- a/patchtree/context.py
+++ b/patchtree/context.py
@@ -1,44 +1,55 @@
from argparse import Namespace
-from typing import Union, IO
+from typing import Union, IO, cast
from pathlib import Path as DiskPath
from zipfile import is_zipfile, Path as ZipPath
+from tempfile import TemporaryFile
from os import path
-from sys import stdout
+from sys import stdout, stderr
+from subprocess import run
-class Context:
- """
- Generic "context" struct that Diff and the main() function depend on for
- application context.
- """
+Path = Union[DiskPath, ZipPath]
+
- fs: Union[DiskPath, ZipPath]
- output: IO = stdout
+class Context:
+ fs: Path
+ output: IO
options: Namespace
+ cache: DiskPath
+
def __init__(self, options: Namespace):
self.options = options
+ self.fs = self.get_fs()
+ self.output = self.get_output()
- target = options.target
- if not path.exists(target):
- raise Exception(f"cannot open `{target}'")
+ if self.options.in_place:
+ location = cast(DiskPath, self.fs)
+ self.cache = location.joinpath(".patchtree.diff")
+ if self.cache.exists():
+ run(
+ ("git", "apply", "--reverse", str(self.cache.absolute())),
+ cwd=str(location.absolute()),
+ )
- if options.out is not None:
- if options.out == "-":
- self.output = stdout
- else:
- self.output = open(options.out, "w+")
+ def __del__(self):
+ self.output.flush()
- if path.isdir(target):
- self.fs = DiskPath(target)
- elif is_zipfile(target):
- self.fs = ZipPath(target)
- else:
- raise Exception("cannot read `{target}'")
+ if self.options.in_place:
+ self.output.seek(0)
+ patch = self.output.read()
+ location = cast(DiskPath, self.fs)
+ if len(patch) > 0:
+ self.cache.write_text(patch)
+ run(
+ ("git", "apply", str(self.cache.absolute())),
+ cwd=str(location.absolute()),
+ )
+
+ self.output.close()
def get_dir(self, dir: str) -> list[str]:
here = self.fs.joinpath(dir)
- print(here)
return [path.name for path in here.iterdir()]
def get_content(self, file: str) -> str | None:
@@ -46,3 +57,33 @@ class Context:
if not here.exists():
return None
return here.read_bytes().decode()
+
+ def get_fs(self) -> Path:
+ target: str = self.options.target
+
+ if not path.exists(target):
+ raise Exception(f"cannot open `{target}'")
+
+ if path.isdir(target):
+ return DiskPath(target)
+
+ if is_zipfile(target):
+ if self.options.in_place:
+ raise Exception("cannot edit zip in-place!")
+ return ZipPath(target)
+
+ raise Exception("cannot read `{target}'")
+
+ def get_output(self) -> IO:
+ if self.options.in_place:
+ if self.options.out is not None:
+ print("warning: --out is ignored when using --in-place", file=stderr)
+ return TemporaryFile("w+")
+
+ if self.options.out is not None:
+ if self.options.out == "-":
+ return stdout
+ else:
+ return open(self.options.out, "w+")
+
+ return stdout