Редактирование конфигов в Python

от автора

Вам когда-нибудь приходилось парсить и программно вносить изменения в чужие конфигурационные файлы? А в файлы с ненормальными форматами вроде того, что у NSD или BIND9? А если формат предусматривает переносы строк, смысловые отступы и сохранение комментариев, задача быстро покидает категорию тривиальных.

Вот почему я делюсь с вами библиотекой python-reconfigure.

Библиотека предоставляет object mapping между текстом конфиг-файла и python-объектами.
Reconfigure никогда не «ломает» файлы, и не стесняется незнакомых блоков и опций внутри, а также сохраняет комментарии.

Сразу перейдем к примеру:

>>> from reconfigure.configs import FSTabConfig >>> from reconfigure.items.fstab import FilesystemData >>> >>> config = FSTabConfig(path='/etc/fstab') >>> config.load() >>> print config.tree {     "filesystems": [         {             "passno": "0",             "device": "proc",             "mountpoint": "/proc",             "freq": "0",             "type": "proc",             "options": "nodev,noexec,nosuid"         },         {             "passno": "1",             "device": "UUID=dfccef1e-d46c-45b8-969d-51391898c55e",             "mountpoint": "/",             "freq": "0",             "type": "ext4",             "options": "errors=remount-ro"         }     ] } >>> tmpfs = FilesystemData() >>> tmpfs.mountpoint = '/srv/cache' >>> tmpfs.type = 'tmpfs' >>> tmpfs.device = 'none' >>> config.tree.filesystems.append(tmpfs) >>> config.save() >>> quit() $ cat /etc/fstab proc    /proc   proc    nodev,noexec,nosuid     0       0 UUID=dfccef1e-d46c-45b8-969d-51391898c55e / ext4 errors=remount-ro 0 1 none    /srv/cache      tmpfs   none    0       0 

Reconfigure — модульная система, и классы *Config скрывают некоторую внутреннюю логику.
Рассмотрим, как предыдущий пример работает «под капотом».
Сначала текст файла преобразуется парсером в абстрактное синтаксическое дерево.

>>> from reconfigure.parsers import SSVParser >>> from reconfigure.builders import BoundBuilder >>> content = open('/etc/fstab').read() >>> syntax_tree = SSVParser().parse(content) >>> syntax_tree <reconfigure.nodes.RootNode object at 0x7f1319eeec50> >>> print syntax_tree (None)         (line)                 (token)                         value = proc                 (token)                         value = /proc                 (token)                         value = proc                 (token)                         value = nodev,noexec,nosuid                 (token)                         value = 0                 (token)                         value = 0         (line)                 (token)                         value = UUID=83810b56-ef4b-44de-85c8-58dc589aef48                 (token)                         value = /                 (token)                         value = ext4                 (token)                         value = errors=remount-ro                 (token)                         value = 0                 (token)                         value = 1 

Затем, класс-строитель (Builder) создает обычные python-объекты и привязывает их к синтаксическому дереву.

>>> builder = BoundBuilder(FSTabData) >>> data_tree = builder.build(syntax_tree) >>> print data_tree {     "filesystems": [         {             "passno": "0",             "device": "proc",             "mountpoint": "/proc",             "freq": "0",Ц             "type": "proc",             "options": "nodev,noexec,nosuid"         },         {             "passno": "1",             "device": "UUID=83810b56-ef4b-44de-85c8-58dc589aef48",             "mountpoint": "/",             "freq": "0",             "type": "ext4",             "options": "errors=remount-ro"         }     ] } 

На самом деле, созданные объекты — это proxy-классы, все поля которых являются свойствами, и при изменении изменяют значения в синтаксическом дереве.

>>> syntax_tree.children[0] <reconfigure.nodes.Node object at 0x7f51c63b9f10> >>> print syntax_tree.children[0] (line)         (token)                 value = proc         (token)                 value = /proc         (token)                 value = proc         (token)                 value = nodev,noexec,nosuid         (token)                 value = 0         (token)                 value = 0  >>> data_tree.filesystems[0].options += ',rw' >>> print syntax_tree.children[0] (line)         (token)                 value = proc         (token)                 value = /proc         (token)                 value = proc         (token)                 value = nodev,noexec,nosuid,rw         (token)                 value = 0         (token)                 value = 0 

Правила привязки элементов дерева к полям классов задаются в классах *Data.
Пример привязок для данных файла /etc/resolv.conf:

from reconfigure.nodes import Node, PropertyNode from reconfigure.items.bound import BoundData   class ResolvData (BoundData):     pass   class ItemData (BoundData):     def template(self):         return Node('line', children=[             Node('token', children=[PropertyNode('value', 'nameserver')]),             Node('token', children=[PropertyNode('value', '8.8.8.8')]),         ])   ResolvData.bind_collection('items', item_class=ItemData) ItemData.bind_property('value', 'name', path=lambda x: x.children[0]) ItemData.bind_property('value', 'value', path=lambda x: x.children[1]) 

Содержимое, синтаксическое и дерево данных этого файла:

>>> print open('/etc/resolv.conf').read() # Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8) #     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN nameserver 127.0.0.1  >>> print syntax_tree (None)         (line) (Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)         DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN)                 (token)                         value = nameserver                 (token)                         value = 127.0.0.1  >>> print data_tree {     "items": [         {             "name": "nameserver",              "value": "127.0.0.1"         }     ] }  

Кроме того, Reconfigure осведомлена о наличии include-директив в некоторых файлах, и запоминает, что в каком файле находилось.

Reconfigure легко расширить собственными парсерами, builder’ами и includer’ами.

В настоящий момент Reconfigure — это сердце Ajenti 1.0 Beta, но об этом в следующий раз 🙂

Github
PYPI
Документация
DEB и RPM пакеты доступны в репозиториях Ajenti

ссылка на оригинал статьи http://habrahabr.ru/post/188248/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *