aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLoek Le Blansch <loek.le-blansch.pv@renesas.com>2025-10-28 09:59:30 +0100
committerLoek Le Blansch <loek.le-blansch.pv@renesas.com>2025-10-28 09:59:30 +0100
commit6afd73e8d1b3aba83799990b5f1afb7f2c36bca3 (patch)
treeb369506f82c08930de2178f5a025260d9a5cc875
parent4fbbf7006193a1baed0b84c50aa4308b0f783183 (diff)
remove diff strategies
-rw-r--r--patchtree/__init__.py4
-rw-r--r--patchtree/config.py14
-rw-r--r--patchtree/diff.py51
-rw-r--r--patchtree/patch.py72
-rw-r--r--patchtree/process.py88
5 files changed, 125 insertions, 104 deletions
diff --git a/patchtree/__init__.py b/patchtree/__init__.py
index f2cc78a..2e304a6 100644
--- a/patchtree/__init__.py
+++ b/patchtree/__init__.py
@@ -1,4 +1,4 @@
from .config import Config, Header
-from .diff import Diff, IgnoreDiff
+from .diff import Diff, File
from .context import Context
-from .process import ProcessCoccinelle, ProcessJinja2
+from .process import ProcessCoccinelle, ProcessJinja2, ProcessIdentity, ProcessExec, ProcessMerge
diff --git a/patchtree/config.py b/patchtree/config.py
index 21cef8b..94b7cc9 100644
--- a/patchtree/config.py
+++ b/patchtree/config.py
@@ -10,15 +10,11 @@ from .process import *
from .diff import *
DEFAULT_PROCESSORS: dict[str, type[Process]] = {
- "jinja": ProcessJinja2,
+ "id": ProcessIdentity,
"cocci": ProcessCoccinelle,
- "smpl": ProcessCoccinelle,
- "touch": ProcessTouch,
+ "jinja": ProcessJinja2,
"exec": ProcessExec,
-}
-
-DEFAULT_DIFFS: dict[str, type[Diff]] = {
- ".gitignore": IgnoreDiff,
+ "merge": ProcessMerge,
}
@@ -89,9 +85,6 @@ class Config:
processors: dict[str, type[Process]] = field(default_factory=lambda: DEFAULT_PROCESSORS)
"""Maps processor specification string to :type:`Process` class type."""
- diff_strategies: dict[str, type[Diff]] = field(default_factory=lambda: DEFAULT_DIFFS)
- """Maps processor specification string to :type:`Process` class type."""
-
header: type[Header] = Header
"""Header class type."""
@@ -104,4 +97,3 @@ class Config:
def __post_init__(self):
self.processors = {**DEFAULT_PROCESSORS, **self.processors}
- self.diff_strategies = {**DEFAULT_DIFFS, **self.diff_strategies}
diff --git a/patchtree/diff.py b/patchtree/diff.py
index fc026bd..7b55353 100644
--- a/patchtree/diff.py
+++ b/patchtree/diff.py
@@ -9,7 +9,7 @@ if TYPE_CHECKING:
@dataclass
-class DiffFile:
+class File:
content: str | None
mode: int
@@ -19,17 +19,15 @@ class DiffFile:
class Diff:
"""
- The base Diff class just produces a regular diff from the (possibly absent)
- original file to the file in the patch input tree. This effectively
- overwrites whatever exists in the target sources with the file in the patch
- input tree.
+ Produce a regular diff from the (possibly absent) original file to the file in the patch input tree. This
+ effectively overwrites whatever exists in the target sources with the file in the patch input tree.
"""
config: Config
file: str
- a: DiffFile
- b: DiffFile
+ a: File
+ b: File
def __init__(self, config: Config, file: str):
self.config = config
@@ -37,7 +35,7 @@ class Diff:
def compare(self) -> str:
"""
- Generate patch text in "git-diff-files -p" format (see
+ Generate delta in "git-diff-files -p" format (see
`<https://git-scm.com/docs/diff-format#generate_patch_text_with_p>`_)
"""
a = self.a
@@ -46,8 +44,6 @@ class Diff:
if a == b:
return ""
- assert (a.content or b.content) is not None
-
fromfile = f"a/{self.file}"
tofile = f"b/{self.file}"
@@ -65,35 +61,10 @@ class Diff:
delta += f"old mode {a.mode:06o}\n"
delta += f"new mode {b.mode:06o}\n"
- lines_a = a.lines()
- lines_b = b.lines()
- diff = unified_diff(lines_a, lines_b, fromfile, tofile, lineterm="", n=self.config.diff_context)
- delta += "".join(f"{line}\n" for line in diff)
+ if a.content != b.content:
+ lines_a = a.lines()
+ lines_b = b.lines()
+ diff = unified_diff(lines_a, lines_b, fromfile, tofile, lineterm="", n=self.config.diff_context)
+ delta += "".join(f"{line}\n" for line in diff)
return delta
-
- def diff(self) -> str:
- return self.compare()
-
-
-class IgnoreDiff(Diff):
- """
- IgnoreDiff is ensures all the lines in the patch source are present in the
- target. This ensures no duplicate ignore lines are added by applying the
- patch.
- """
-
- def diff(self):
- lines_a = self.a.lines()
- lines_b = self.b.lines()
-
- add_lines = set(lines_b) - set(lines_a)
-
- self.b.content = "\n".join(
- (
- *lines_a,
- *add_lines,
- )
- )
-
- return self.compare()
diff --git a/patchtree/patch.py b/patchtree/patch.py
index 3c50bb9..32d3b7f 100644
--- a/patchtree/patch.py
+++ b/patchtree/patch.py
@@ -3,10 +3,10 @@ from typing import TYPE_CHECKING
from pathlib import Path
-from .diff import Diff, DiffFile
+from .diff import Diff, File
+from .process import Process
if TYPE_CHECKING:
- from .process import Process
from .context import Context
from .config import Config
@@ -16,63 +16,47 @@ class Patch:
patch: Path
file: str
- file_name: str = ""
- file_type: str = ""
- processors: list[str] = []
+ processors: list[tuple[type[Process], Process.Args]] = []
def __init__(self, config: Config, patch: Path):
self.patch = patch
self.config = config
- self.file_name = patch.name
-
- # find preprocessors
- idx = self.file_name.find(config.process_delimiter)
- if idx >= 0:
- self.processors = self.file_name[idx:].split(config.process_delimiter)
- self.processors = [template.strip() for template in self.processors]
- self.processors = [template for template in self.processors if len(template) > 0]
- self.processors.reverse()
- self.file_name = self.file_name[:idx]
-
- # save the path to the target file
- self.file = str(patch.parent.joinpath(self.file_name))
-
- # find and split at file extension
- idx = self.file_name.find(".")
- if idx >= 0:
- self.file_type = self.file_name[idx:]
- self.file_name = self.file_name[:idx]
-
- def get_diff(self) -> type[Diff]:
- return self.config.diff_strategies.get(self.file_type, Diff)
-
- def get_processors(self) -> list[type[Process]]:
- processors = []
- for processor in self.processors:
- if processor not in self.config.processors:
- continue
- processors.append(self.config.processors[processor])
- return processors
+ self.processors.clear()
+ self.file, *proc_strs = str(patch).split(config.process_delimiter)
+ for proc_str in proc_strs:
+ proc_name, *argv = proc_str.split(",")
+ args = Process.Args(name=proc_name, argv=argv)
+ proc_cls = config.processors.get(proc_name, None)
+ if proc_cls is None:
+ raise Exception(f"unknown processor: `{proc_cls}'")
+ for arg in argv:
+ key, value, *_ = (*arg.split("=", 1), None)
+ args.argd[key] = value
+ self.processors.insert(
+ 0,
+ (
+ proc_cls,
+ args,
+ ),
+ )
def write(self, context: Context) -> None:
- diff_class = self.get_diff()
- processor_classes = self.get_processors()
-
- diff = diff_class(self.config, self.file)
+ diff = Diff(self.config, self.file)
- diff.a = DiffFile(
+ diff.a = File(
content=context.get_content(self.file),
mode=context.get_mode(self.file),
)
- diff.b = DiffFile(
+ diff.b = File(
content=self.patch.read_text(),
mode=self.patch.stat().st_mode,
)
- for processor_class in processor_classes:
- processor = processor_class(context)
+
+ for cls, args in self.processors:
+ processor = cls(context, args)
diff.b = processor.transform(diff.a, diff.b)
- delta = diff.diff()
+ delta = diff.compare()
context.output.write(delta)
diff --git a/patchtree/process.py b/patchtree/process.py
index c5cd339..3530307 100644
--- a/patchtree/process.py
+++ b/patchtree/process.py
@@ -1,13 +1,14 @@
from __future__ import annotations
-from typing import TYPE_CHECKING, Any
+from typing import TYPE_CHECKING, Any, Callable
from tempfile import mkstemp
from jinja2 import Environment
from subprocess import Popen, run
from pathlib import Path
from shlex import split as shell_split
+from dataclasses import dataclass, field
-from .diff import DiffFile
+from .diff import File
if TYPE_CHECKING:
from .context import Context
@@ -21,10 +22,19 @@ class Process:
context: Context
"""Patch file context."""
- def __init__(self, context: Context):
+ @dataclass
+ class Args:
+ name: str
+ argv: list[str] = field(default_factory=list)
+ argd: dict[str, str | None] = field(default_factory=dict)
+
+ args: Args
+
+ def __init__(self, context: Context, args: Args):
+ self.args = args
self.context = context
- def transform(self, a: DiffFile, b: DiffFile) -> DiffFile:
+ def transform(self, a: File, b: File) -> File:
"""
Transform the input file.
@@ -45,6 +55,12 @@ class ProcessJinja2(Process):
lstrip_blocks=True,
)
+ def __init__(self, *args, **kwargs):
+ super(ProcessJinja2, self).__init__(*args, **kwargs)
+
+ if len(self.args.argv) > 0:
+ raise Exception("too many arguments")
+
def transform(self, a, b):
template_vars = self.get_template_vars()
assert b.content is not None
@@ -60,6 +76,12 @@ class ProcessCoccinelle(Process):
Coccinelle transformer.
"""
+ def __init__(self, *args, **kwargs):
+ super(ProcessCoccinelle, self).__init__(*args, **kwargs)
+
+ if len(self.args.argv) > 0:
+ raise Exception("too many arguments")
+
def transform(self, a, b):
content_a = a.content or ""
content_b = b.content or ""
@@ -95,13 +117,13 @@ class ProcessCoccinelle(Process):
return b
-class ProcessTouch(Process):
+class ProcessIdentity(Process):
"""
- Touch transformer.
+ Identity transformer.
"""
def transform(self, a, b):
- return DiffFile(content=a.content, mode=b.mode)
+ return File(content=a.content, mode=b.mode)
class ProcessExec(Process):
@@ -109,6 +131,12 @@ class ProcessExec(Process):
Executable transformer.
"""
+ def __init__(self, *args, **kwargs):
+ super(ProcessExec, self).__init__(*args, **kwargs)
+
+ if len(self.args.argv) > 0:
+ raise Exception("too many arguments")
+
def transform(self, a, b):
assert b.content is not None
@@ -127,3 +155,49 @@ class ProcessExec(Process):
exec.unlink()
return b
+
+
+class ProcessMerge(Process):
+ """
+ Merge transformer.
+ """
+
+ def merge_ignore(self, a: File, b: File) -> File:
+ lines_a = a.lines()
+ lines_b = b.lines()
+
+ add_lines = set(lines_b) - set(lines_a)
+
+ b.content = "\n".join(
+ (
+ *lines_a,
+ *add_lines,
+ )
+ )
+
+ return b
+
+ strategies: dict[str, Callable[[ProcessMerge, File, File], File]] = {
+ "ignore": merge_ignore,
+ }
+
+ strategy: Callable[[ProcessMerge, File, File], File]
+
+ def __init__(self, *args, **kwargs):
+ super(ProcessMerge, self).__init__(*args, **kwargs)
+
+ argv = self.args.argv
+ if len(argv) < 1:
+ raise Exception("not enough arguments")
+
+ if len(argv) > 1:
+ raise Exception("too many arguments")
+
+ strategy = argv[0]
+ if strategy not in self.strategies:
+ raise Exception(f"unknown merge strategy: `{strategy}'")
+
+ self.strategy = self.strategies[strategy]
+
+ def transform(self, a, b):
+ return self.strategy(self, a, b)