FIX: Pylint reduce
[csit.git] / resources / tools / block_replacer / replace.py
1 #!/usr/bin/env python3
2
3 # Copyright (c) 2021 Cisco and/or its affiliates.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16 """A script simplifying replacement of blocks of lines.
17
18 A bash solution created by combining these two:
19     https://unix.stackexchange.com/a/181215
20     https://stackoverflow.com/a/23849180
21 does not seem to work if the blocks contain complicated characters.
22 """
23
24 import argparse
25 import os
26 import tempfile
27
28 def main():
29     """Main function for the block replacing script."""
30
31     description = '''Replace a block of lines with another block.
32
33     Both block-to-replace and replacing-block are read from a file.
34     The replacement is performed on a file, in-place.
35     Only first block occurence is replaced.
36     If the block-to-replace is preceded by a partial match,
37     it may not be recognized.
38
39     The current implementation uses temporary files,
40     created in the working directory.
41     if something fails, thise temporary files need to be deleted manually.
42
43     TODO: Preserve target attributes. Maybe https://pypi.org/project/in-place/
44 '''
45     parser = argparse.ArgumentParser(description)
46     parser.add_argument(
47         u"before", type=str,
48         help=u"Path to file containing the old content to replace."
49     )
50     parser.add_argument(
51         u"after", type=str,
52         help=u"Path to file containing the new content to replace with."
53     )
54     parser.add_argument(
55         u"targets", metavar=u"target", nargs=u"+", type=str,
56         help=u"Paths to file where the replacement should be made."
57     )
58     args = parser.parse_args()
59
60     do_it(args)
61
62
63 def do_it(args):
64     """Read contents, create edited target, replace the original target with it.
65
66     :param args: Parsed command line arguments.
67     :type args: Object (typically argparse.Namespace) which contains
68         "before", "after" and "target" fields.
69     """
70     with open(args.before, u"r") as file_in:
71         content_before = file_in.readlines()
72     before_len = len(content_before)
73     with open(args.after, u"r") as file_in:
74         content_after = file_in.readlines()
75
76     for target in args.targets:
77         with tempfile.NamedTemporaryFile(
78             dir=u".", mode=u"w", delete=False
79         ) as file_out:
80             with open(target, u"r") as file_in:
81                 # Phase one, searching for content, copying what does not match.
82                 buffer_lines = list()
83                 line_index_to_check = 0
84                 content_found = False
85                 while 1:
86                     line_in = file_in.readline()
87                     if not line_in:
88                         print(f"{target}: Content not found.")
89                         for line_out in buffer_lines:
90                             file_out.write(line_out)
91                         buffer_lines = list()
92                         break
93                     if line_in != content_before[line_index_to_check]:
94                         line_index_to_check = 0
95                         if buffer_lines:
96                             for line_out in buffer_lines:
97                                 file_out.write(line_out)
98                             buffer_lines = list()
99                         file_out.write(line_in)
100                         continue
101                     buffer_lines.append(line_in)
102                     line_index_to_check += 1
103                     if line_index_to_check < before_len:
104                         continue
105                     # Buffer has the match! Do not write it.
106                     content_found = True
107                     break
108                 if not content_found:
109                     file_out.close()
110                     os.remove(file_out.name)
111                     continue
112                 # Phase two, write the replacement instead.
113                 for line_out in content_after:
114                     file_out.write(line_out)
115                 # Phase three, copy the rest of the file.
116                 while 1:
117                     line_in = file_in.readline()
118                     if not line_in:
119                         print(f"{target}: Replacement done.")
120                         break
121                     file_out.write(line_in)
122         os.replace(file_out.name, target)
123
124
125 if __name__ == u"__main__":
126     main()