Separate files needing GPL license
[csit.git] / resources / tools / block_replacer / replace.py
diff --git a/resources/tools/block_replacer/replace.py b/resources/tools/block_replacer/replace.py
new file mode 100644 (file)
index 0000000..02d1889
--- /dev/null
@@ -0,0 +1,126 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2020 Cisco and/or its affiliates.
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""A script simplifying replacement of blocks of lines.
+
+A bash solution created by combining these two:
+    https://unix.stackexchange.com/a/181215
+    https://stackoverflow.com/a/23849180
+does not seem to work if the blocks contain complicated characters.
+"""
+
+import argparse
+import os
+import tempfile
+
+def main():
+    """Main function for the block replacing script."""
+
+    description = '''Replace a block of lines with another block.
+
+    Both block-to-replace and replacing-block are read from a file.
+    The replacement is performed on a file, in-place.
+    Only first block occurence is replaced.
+    If the block-to-replace is preceded by a partial match,
+    it may not be recognized.
+
+    The current implementation uses temporary files,
+    created in the working directory.
+    if something fails, thise temporary files need to be deleted manually.
+
+    TODO: Preserve target attributes. Maybe https://pypi.org/project/in-place/
+'''
+    parser = argparse.ArgumentParser(description)
+    parser.add_argument(
+        u"before", type=str,
+        help=u"Path to file containing the old content to replace."
+    )
+    parser.add_argument(
+        u"after", type=str,
+        help=u"Path to file containing the new content to replace with."
+    )
+    parser.add_argument(
+        u"targets", metavar=u"target", nargs=u"+", type=str,
+        help=u"Paths to file where the replacement should be made."
+    )
+    args = parser.parse_args()
+
+    do_it(args)
+
+
+def do_it(args):
+    """Read contents, create edited target, replace the original target with it.
+
+    :param args: Parsed command line arguments.
+    :type args: Object (typically argparse.Namespace) which contains
+        "before", "after" and "target" fields.
+    """
+    with open(args.before, u"r") as file_in:
+        content_before = file_in.readlines()
+    before_len = len(content_before)
+    with open(args.after, u"r") as file_in:
+        content_after = file_in.readlines()
+
+    for target in args.targets:
+        with tempfile.NamedTemporaryFile(
+            dir=u".", mode=u"w", delete=False
+        ) as file_out:
+            with open(target, u"r") as file_in:
+                # Phase one, searching for content, copying what does not match.
+                buffer_lines = list()
+                line_index_to_check = 0
+                content_found = False
+                while 1:
+                    line_in = file_in.readline()
+                    if not line_in:
+                        print(f"{target}: Content not found.")
+                        for line_out in buffer_lines:
+                            file_out.write(line_out)
+                        buffer_lines = list()
+                        break
+                    if line_in != content_before[line_index_to_check]:
+                        line_index_to_check = 0
+                        if buffer_lines:
+                            for line_out in buffer_lines:
+                                file_out.write(line_out)
+                            buffer_lines = list()
+                        file_out.write(line_in)
+                        continue
+                    buffer_lines.append(line_in)
+                    line_index_to_check += 1
+                    if line_index_to_check < before_len:
+                        continue
+                    # Buffer has the match! Do not write it.
+                    content_found = True
+                    break
+                if not content_found:
+                    file_out.close()
+                    os.remove(file_out.name)
+                    continue
+                # Phase two, write the replacement instead.
+                for line_out in content_after:
+                    file_out.write(line_out)
+                # Phase three, copy the rest of the file.
+                while 1:
+                    line_in = file_in.readline()
+                    if not line_in:
+                        print(f"{target}: Replacement done.")
+                        break
+                    file_out.write(line_in)
+        os.replace(file_out.name, target)
+
+
+if __name__ == u"__main__":
+    main()