У меня есть общая структура потокового редактора для такого рода вещей.Я загружаю файл в память, применяю изменения к списку строк в памяти и выписываю файл, если были внесены изменения.
У меня есть шаблон, который выглядит следующим образом:
from sed_util import delete_range, insert_range, append_range, replace_range
def sed(filename):
modified = 0
# Load file into memory
with open(filename) as f:
lines = [line.rstrip() for line in f]
# magic here...
if modified:
with open(filename, "w") as f:
for line in lines:
f.write(line + "\n")
А в разделе # magic here
у меня есть:
модификаций для отдельныхстроки, например:
lines[i] = change_line(lines[i])
вызывает мои утилиты sed для вставки, добавления и замены строк, например:
lines = delete_range(lines, some_range)
Последний использует примитивы, подобные этим:
def delete_range(lines, r):
"""
>>> a = list(range(10))
>>> b = delete_range(a, (1, 3))
>>> b
[0, 4, 5, 6, 7, 8, 9]
"""
start, end = r
assert start <= end
return [line for i, line in enumerate(lines) if not (start <= i <= end)]
def insert_range(lines, line_no, new_lines):
"""
>>> a = list(range(10))
>>> b = list(range(11, 13))
>>> c = insert_range(a, 3, b)
>>> c
[0, 1, 2, 11, 12, 3, 4, 5, 6, 7, 8, 9]
>>> c = insert_range(a, 0, b)
>>> c
[11, 12, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> c = insert_range(a, 9, b)
>>> c
[0, 1, 2, 3, 4, 5, 6, 7, 8, 11, 12, 9]
"""
assert 0 <= line_no < len(lines)
return lines[0:line_no] + new_lines + lines[line_no:]
def append_range(lines, line_no, new_lines):
"""
>>> a = list(range(10))
>>> b = list(range(11, 13))
>>> c = append_range(a, 3, b)
>>> c
[0, 1, 2, 3, 11, 12, 4, 5, 6, 7, 8, 9]
>>> c = append_range(a, 0, b)
>>> c
[0, 11, 12, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> c = append_range(a, 9, b)
>>> c
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12]
"""
assert 0 <= line_no < len(lines)
return lines[0:line_no+1] + new_lines + lines[line_no+1:]
def replace_range(lines, line_nos, new_lines):
"""
>>> a = list(range(10))
>>> b = list(range(11, 13))
>>> c = replace_range(a, (0, 2), b)
>>> c
[11, 12, 2, 3, 4, 5, 6, 7, 8, 9]
>>> c = replace_range(a, (8, 10), b)
>>> c
[0, 1, 2, 3, 4, 5, 6, 7, 11, 12]
>>> c = replace_range(a, (0, 10), b)
>>> c
[11, 12]
>>> c = replace_range(a, (0, 10), [])
>>> c
[]
>>> c = replace_range(a, (0, 9), [])
>>> c
[9]
"""
start, end = line_nos
return lines[:start] + new_lines + lines[end:]
def find_line(lines, regex):
for i, line in enumerate(lines):
if regex.match(line):
return i
if __name__ == '__main__':
import doctest
doctest.testmod()
Для ясности тесты работают с массивами целых чисел, но преобразования работают и с массивами строк.
Обычно я сканирую список строк, чтобы определить изменения, которые я хочу применить, обычно с помощью регулярных выражений, а затем применяю изменения к сопоставимым данным.Например, сегодня я закончил с 2000 изменений строк в 150 файлах.
Это работает лучше, чем sed
, когда вам нужно применить многострочные шаблоны или дополнительную логику, чтобы определить, применимо ли изменение.