Вам когда-нибудь приходилось парсить и программно вносить изменения в чужие конфигурационные файлы? А в файлы с ненормальными форматами вроде того, что у 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/
Добавить комментарий