File: rst_batch_edit.py

package info (click to toggle)
blender-doc 4.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 253,604 kB
  • sloc: python: 13,030; javascript: 322; makefile: 113; sh: 107
file content (347 lines) | stat: -rwxr-xr-x 10,102 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
#!/usr/bin/env python3
# Apache License, Version 2.0

# DEVELOPER NOTE:
#
# This script should be cleaned up and is more of grab-back of functions
# which can be useful for maintenance.
#
# Not a general command line tools for general use.
#
# While it's not to bad to keep it for now, consider changing how this is exposed.
# ~ ideasman42

import os
# if you want to operate on a subdir, e.g: "render"
SUBDIR = ""
CURRENT_DIR = os.path.abspath(os.path.dirname(__file__))
RST_DIR = os.path.normpath(os.path.join(CURRENT_DIR, "..", "..", "manual", SUBDIR))


def rst_files(path):
    for dirpath, dirnames, filenames in os.walk(path):
        if dirpath.startswith("."):
            continue
        for filename in filenames:
            if filename.startswith("."):
                continue
            ext = os.path.splitext(filename)[1]
            if ext.lower() == ".rst":
                yield os.path.join(dirpath, filename)


def main():
    for fn in rst_files(RST_DIR):
        with open(fn, "r", encoding="utf-8") as f:
            data_src = f.read()
            data_dst = operation(fn, data_src)

        if data_dst is None or (data_src == data_dst):
            continue

        with open(fn, "w", encoding="utf-8") as f:
            data_src = f.write(data_dst)


# ---------------------------------------
# Custom Code - do whatever you like here
# (only commit reusable examples).
#
# Functions take a (file_name, file_contents)
# returns the new file_contents (or None to skip the operation)


def preset_strip_trailing_space(fn, data_src):
    """
    Strips trailing white-space from all RST files.
    """

    lines = data_src.split("\n")

    for i, l in enumerate(lines):
        l = l.rstrip()
        lines[i] = l

    data_dst = "\n".join(lines)

    # 2 empty lines max
    data_dst = data_dst.replace("\n\n\n\n", "\n\n\n")
    return data_dst


def preset_find_and_replace(fn, data_src):
    """
    Simply finds and replaces text
    """
    # handy for replacing exact words
    use_whole_words = False

    find_replace_pairs = (
        ("−", "-"),
    )

    if use_whole_words:
        import re
        find_replace_pairs_re = [
            (re.compile("\b" + src + "\b"), dst)
            for src, dst in find_replace_pairs
        ]

    lines = data_src.split("\n")

    for i, l in enumerate(lines):
        old_l = l
        do_replace = False
        if use_whole_words:
            for src_re, dst in find_replace_pairs_re:
                l = re.sub(src_re, dst, l)
        else:
            for src, dst in find_replace_pairs:
                l = l.replace(src, dst)

        if old_l != l:
            lines[i] = l
            # print ("Replaced:", old_l, "\n    With:", l, "\n")

    data_dst = "\n".join(lines)
    return data_dst


def preset_replace_table(fn, data_src):
    """
    Replace ASCII tables with list-table.
    """

    lines = data_src.split("\n")

    is_table = -1
    tot_row = -1

    def is_div(l):
        return l.startswith("+") and l.endswith("+") and set(l) == {"+", "-"}

    i = 0
    while i < len(lines):
        l = lines[i]
        l = l.strip()
        if is_table == -1:
            if is_div(l):
                is_table = i
                tot_row = l.count("+") - 1
        else:
            if l.startswith(("+", "|")) and l.endswith(("+", "|")):
                pass
            else:
                # table is [is_table : i]
                table_content = []

                def add_col():
                    table_content.append([[] for k in range(tot_row)])

                add_col()
                tot_indent = len(lines[is_table]) - len(lines[is_table].lstrip())
                for j in range(is_table + 1, i - 1):
                    l = lines[j].strip()
                    # print(l)
                    if is_div(l):
                        add_col()
                    else:
                        for ir, r in enumerate(l[1:-1].split("|")):
                            table_content[-1][ir].append(r.rstrip())

                # optionally complain about single cell-tables
                if tot_row == 1 and len(table_content) == 1:
                    raise Exception(fn + ":" + str(i + 1))

                indent = " " * tot_indent
                indent_dot = indent + (" " * 3)
                indent_item = indent_dot + (" " * 4)

                list_table = [indent + ".. list-table::", ""]

                for col in table_content:
                    for ir in range(tot_row):
                        if ir >= len(col):
                            data = [""]
                        else:
                            data = col[ir]
                            if not data:
                                data = [""]

                        if ir == 0:
                            list_table.append(indent_dot + "* - " + data[0])
                        else:
                            list_table.append(indent_dot + "  - " + data[0])

                        for d in data[1:]:
                            list_table.append(indent_item + d)
                        # figures need blank space between bullets, for now just add for all,
                        # can remove manually later if we want.
                        list_table.append("")

                # ensure newlines before & after
                list_table = [""] + [lt.rstrip() for lt in list_table] + [""]

                # no double-blank lines
                li = 1
                while li < len(list_table):
                    if list_table[li] == "":
                        if list_table[li - 1] == "":
                            del list_table[li]
                            li -= 1
                            continue
                    li += 1

                if 1:
                    lines[is_table - 1:i] = list_table
                    i = is_table
                    is_table = -1

        i += 1

    data_dst = "\n".join(lines)

    # 2 empty lines max
    data_dst = data_dst.replace("\n\n\n\n", "\n\n\n")
    return data_dst


def preset_wrap_lines(fn, data_src):
    """
    Wrap long lines, attempt to split on delimiters.
    """

    # ideal margin
    MARGIN_TARGET = 75
    # max allowable margin
    MARGIN_MAX = 118
    # ignore lines shorter
    MARGIN_MIN = 20

    lines = data_src.split("\n")

    i = 0
    while i < len(lines):
        l_orig = lines[i].rstrip()
        l = l_orig.lstrip()
        if len(l_orig) >= MARGIN_MAX:

            # ignore directives since their formatting can't always be split
            if l.lstrip(" *-").startswith(".. "):
                print("Ignoring %s:%d: " % (fn, i + 1))
                print(l_orig)
                i += 1
                continue

            if l.startswith("#. "):
                indent = 3
            elif l.startswith("- "):
                indent = 2
            elif l.startswith("| "):
                indent = 2
            else:
                indent = 0
            indent += len(l_orig) - len(l)

            index_best = -1
            index_weight_best = 1000000.0
            c_best = ""

            # now attempt to split the line
            # lower values are weighted to wrap
            for c, w in (
                    (". ", 0.2),
                    ("? ", 0.2),
                    ("! ", 0.2),

                    ("; ", 0.5),
                    (", ", 1.0),
                    (" (", 0.75),
                    (") ", 0.75),
                    ("- ", 2.0),
                    (" :", 2.0),
                    # last resort
                    (" ", 10.0),
            ):

                index = l_orig[:(MARGIN_TARGET + MARGIN_MAX) // 2].rfind(c)
                if index == -1:
                    index = l_orig[:MARGIN_MAX - 1].rfind(c)

                if index > MARGIN_MIN and index < MARGIN_MAX:
                    # either align with the target length or split the line in half
                    index_weight = min(abs(index - MARGIN_TARGET),
                                       abs(index - (len(l_orig) // 2)))
                    index_weight = (index_weight + 1) * w
                    if index_weight < index_weight_best:
                        index_weight_best = index_weight
                        index_best = index
                        c_best = c

            if index_best != -1:
                index_best += 1
                lines[i:i + 1] = [l_orig[:index_best].rstrip(), (indent * " ") + l_orig[index_best:].lstrip()]
                i -= 1
            else:
                print("Not found %s:%d: " % (fn, i + 1))
                print(l_orig)

        # lines[i] = l
        i += 1

    data_dst = "\n".join(lines)
    return data_dst


def preset_help(operations):
    """
    Shows help text.
    """
    import textwrap
    print("Operations:\n")
    for op, op_arg, op_fn in operations:
        print("%s:" % op_arg)
        print(textwrap.indent(op_fn.__doc__.strip(), "  "))
        print()


def operation_from_args():
    import sys
    namespace = globals()

    operations = []
    for op, op_fn in namespace.items():
        if op.startswith("preset_"):
            if callable(op_fn):
                op_arg = "--%s" % op[7:]
                operations.append((op, op_arg, op_fn))
    operations.sort()

    operations_map = {op_arg: (op, op_fn) for (op, op_arg, op_fn) in operations}

    operation = None
    for arg in sys.argv:
        if arg.startswith("--"):
            op, op_fn = operations_map.get(arg, (None, None))
            if op is not None:
                operation = op_fn
                break
            else:
                print("Argument '%s' not in %s" % (arg, " ".join(sorted(operations_map.keys()))))
                return None

    if operation is None:
        print("No command passed")
    elif operation is preset_help:
        preset_help(operations)
        operation = None
    return operation


# define the operation to call
# operation = preset_strip_trailing_space
operation = operation_from_args()

if __name__ == "__main__":
    if operation is not None:
        main()