Конвертировать lvm.conf в python dict, используя pyparsing - PullRequest
0 голосов
/ 03 февраля 2019

Я пытаюсь преобразовать lvm.conf в объект Python (как в JSON).Файл конфигурации LVM (Logical Volume Management) выглядит следующим образом:

# Configuration section config.
# How LVM configuration settings are handled.
config {

    # Configuration option config/checks.
    # If enabled, any LVM configuration mismatch is reported.
    # This implies checking that the configuration key is understood by
    # LVM and that the value of the key is the proper type. If disabled,
    # any configuration mismatch is ignored and the default value is used
    # without any warning (a message about the configuration key not being
    # found is issued in verbose mode only).
    checks = 1

    # Configuration option config/abort_on_errors.
    # Abort the LVM process if a configuration mismatch is found.
    abort_on_errors = 0

    # Configuration option config/profile_dir.
    # Directory where LVM looks for configuration profiles.
    profile_dir = "/etc/lvm/profile"
}


local {
}
log {
    verbose=0
    silent=0
    syslog=1
    overwrite=0
    level=0
    indent=1
    command_names=0
    prefix="  "
    activation=0
    debug_classes=["memory","devices","activation","allocation","lvmetad","metadata","cache","locking","lvmpolld","dbus"]
}

Я хотел бы получить Python dict, например:

{ "section_name"": 
{"value1" : 1,
 "value2" : "some_string",
 "value3" : [list, of, strings]}... and so on.}

Функция синтаксического анализатора:

def parseLvmConfig2(path="/etc/lvm/lvm.conf"):
    try:
        EQ, LBRACE, RBRACE, LQ, RQ = map(pp.Suppress, "={}[]")
        comment = pp.Suppress("#") + pp.Suppress(pp.restOfLine)
        configSection = pp.Word(pp.alphas + "_") + LBRACE
        sectionKey = pp.Word(pp.alphas + "_")
        sectionValue = pp.Forward()
        entry = pp.Group(sectionKey + EQ + sectionValue)
        real = pp.Regex(r"[+-]?\d+\.\d*").setParseAction(lambda x: float(x[0]))
        integer = pp.Regex(r"[+-]?\d+").setParseAction(lambda x: int(x[0]))
        listval = pp.Regex(r'(?:\[)(.*)?(?:\])').setParseAction(lambda x: eval(x[0]))

        pp.dblQuotedString.setParseAction(pp.removeQuotes)

        struct = pp.Group(pp.ZeroOrMore(entry) + RBRACE)
        sectionValue << (pp.dblQuotedString | real | integer | listval)
        parser = pp.ZeroOrMore(configSection + pp.Dict(struct))
        res = parser.parseFile(path)
        print(res)
    except (pp.ParseBaseException, ) as e:
        print("lvm.conf bad format {0}".format(e))

Результат грязный, и вопрос в том, как заставить pyparsing выполнять свою работу без дополнительной логики?

ОБНОВЛЕНИЕ (решено):

Для тех, кто хочет лучше понять отображение , пожалуйста, ознакомьтесь с объяснением @PaulMcG ниже.(Спасибо за pyparsing, Пол!)

import pyparsing as pp
def parseLvmConf(conf="/etc/lvm/lvm.conf", res_type="dict"):
    EQ, LBRACE, RBRACE, LQ, RQ = map(pp.Suppress, "={}[]")
    comment = "#" + pp.restOfLine
    integer = pp.nums
    real = pp.Word(pp.nums + "." + pp.nums)
    pp.dblQuotedString.setParseAction(pp.removeQuotes)
    scalar_value = real | integer | pp.dblQuotedString
    list_value = pp.Group(LQ + pp.delimitedList(scalar_value) + RQ)
    key = pp.Word(pp.alphas + "_", pp.alphanums + '_')
    key_value = pp.Group(key + EQ + (scalar_value | list_value))
    struct = pp.Forward()
    entry = key_value | pp.Group(key + struct)
    struct <<= pp.Dict(LBRACE + pp.ZeroOrMore(entry) + RBRACE)
    parser = pp.Dict(pp.ZeroOrMore(entry))
    parser.ignore(comment)
    try:
        #return lvm.conf as dict
        if res_type == "dict":
            return parser.parseFile(conf).asDict()
        # return lvm.conf as list
        elif res_type == "list":
            return parser.parseFile(conf).asList()
        else:
            #return lvm.conf as ParseResults
            return parser.parseFile(conf)
    except (pp.ParseBaseException,) as e:
        print("lvm.conf bad format {0}".format(e))

1 Ответ

0 голосов
/ 04 февраля 2019

Шаг 1 всегда должен заключаться в том, чтобы, по крайней мере, набросать BNF для формата, который вы собираетесь анализировать.Это действительно помогает организовать ваши мысли и заставляет задуматься о структуре и данных, которые вы анализируете, прежде чем приступить к написанию реального кода.

Вот BNF, который я придумал для этой конфигурации (похоже, чтоСтрока Python, потому что это облегчает вставку в ваш код для дальнейшего использования - но pyparsing не работает с такими строками или не требует их, они являются просто инструментом проектирования):

BNF = '''
    key_struct ::= key struct
    struct ::= '{' (key_value | key_struct)... '}'
    key_value ::= key '=' (scalar_value | list_value)
    key ::= word composed of alphas and '_'
    list_value ::= '[' scalar_value [',' scalar_value]... ']'
    scalar_value ::= real | integer | double-quoted-string
    comment ::= '#' rest-of-line
'''

Обратите внимание, что открытие и закрытие{} и [] находятся на одном уровне, вместо того, чтобы открывать в одном выражении и ближе в другом.

Этот BNF также учитывает структуры, вложенные в структуры, что не является строготребуется в примере текста, который вы разместили, но так как ваш код, по-видимому, поддерживает это, я включил его.

Отсюда довольно просто выполнить перевод на pyparsing, работая снизу вверх через BNF:

EQ, LBRACE, RBRACE, LQ, RQ = map(pp.Suppress, "={}[]")
comment = "#" + pp.restOfLine

integer = ppc.integer  #pp.Regex(r"[+-]?\d+").setParseAction(lambda x: int(x[0]))
real = ppc.real  #pp.Regex(r"[+-]?\d+\.\d*").setParseAction(lambda x: float(x[0]))
pp.dblQuotedString.setParseAction(pp.removeQuotes)
scalar_value = real | integer | pp.dblQuotedString

# `delimitedList(expr)` is a shortcut for `expr + ZeroOrMore(',' + expr)`
list_value = pp.Group(LQ + pp.delimitedList(scalar_value) + RQ)

key = pp.Word(pp.alphas + "_", pp.alphanums + '_')
key_value = pp.Group(key + EQ + (scalar_value | list_value))

struct = pp.Forward()
entry = key_value | pp.Group(key + struct)
struct <<= (LBRACE + pp.ZeroOrMore(entry) + RBRACE)
parser = pp.ZeroOrMore(entry)
parser.ignore(comment)

Запуск этого кода:

try:
    res = parser.parseString(lvm_source)
    # print(res.dump())
    res.pprint()
    return res
except (pp.ParseBaseException, ) as e:
    print("lvm.conf bad format {0}".format(e))

Дает этот вложенный список:

[['config',
  ['checks', 1],
  ['abort_on_errors', 0],
  ['profile_dir', '/etc/lvm/profile']],
 ['local'],
 ['log',
  ['verbose', 0],
  ['silent', 0],
  ['syslog', 1],
  ['overwrite', 0],
  ['level', 0],
  ['indent', 1],
  ['command_names', 0],
  ['prefix', '  '],
  ['activation', 0],
  ['debug_classes',
   ['memory',
    'devices',
    'activation',
    'allocation',
    'lvmetad',
    'metadata',
    'cache',
    'locking',
    'lvmpolld',
    'dbus']]]]

Я думаю, что формат выпредпочтительным является тот, где вы можете получить доступ к значениям в качестве ключей во вложенном dict или в иерархическом объекте.В Pyparsing есть класс Dict, который будет делать это во время разбора, поэтому имена результатов автоматически назначаются для вложенных подгрупп.Измените эти две строки, чтобы их вложенные записи автоматически определялись:

struct <<= pp.Dict(LBRACE + pp.ZeroOrMore(entry) + RBRACE)
parser = pp.Dict(pp.ZeroOrMore(entry))

Теперь, если мы вызовем dump () вместо pprint (), мы увидим иерархическое именование:

[['config', ['checks', 1], ['abort_on_errors', 0], ['profile_dir', '/etc/lvm/profile']], ['local'], ['log', ['verbose', 0], ['silent', 0], ['syslog', 1], ['overwrite', 0], ['level', 0], ['indent', 1], ['command_names', 0], ['prefix', '  '], ['activation', 0], ['debug_classes', ['memory', 'devices', 'activation', 'allocation', 'lvmetad', 'metadata', 'cache', 'locking', 'lvmpolld', 'dbus']]]]
- config: [['checks', 1], ['abort_on_errors', 0], ['profile_dir', '/etc/lvm/profile']]
  - abort_on_errors: 0
  - checks: 1
  - profile_dir: '/etc/lvm/profile'
- local: ''
- log: [['verbose', 0], ['silent', 0], ['syslog', 1], ['overwrite', 0], ['level', 0], ['indent', 1], ['command_names', 0], ['prefix', '  '], ['activation', 0], ['debug_classes', ['memory', 'devices', 'activation', 'allocation', 'lvmetad', 'metadata', 'cache', 'locking', 'lvmpolld', 'dbus']]]
  - activation: 0
  - command_names: 0
  - debug_classes: ['memory', 'devices', 'activation', 'allocation', 'lvmetad', 'metadata', 'cache', 'locking', 'lvmpolld', 'dbus']
  - indent: 1
  - level: 0
  - overwrite: 0
  - prefix: '  '
  - silent: 0
  - syslog: 1
  - verbose: 0

Затем вы можете получить доступ к полям как res['config']['checks'] или res.log.indent.

...