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

в 17:17, , рубрики: linux, python, конфигурационные файлы, Программирование, метки: ,

Редактирование конфигов в 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

Автор: hardex

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js