Чтение config.js на языке Python

#javascript #python #config

Вопрос:

Я работаю в проекте, использующем как javascript, так и python. Мой код на python должен уметь читать config.js файл и получить оттуда IP-адреса, пароли, хосты…. Я не могу каким-либо образом изменить config.js файл.

Я пробовал использовать slimit, но он смешивает некоторые поля, поэтому я не могу правильно получить доступ к своим данным. Это может быть связано с тем, что некоторые данные действительно вложены, и через config.js файл.

Файл конфигурации выглядит так (но на самом деле намного длиннее)

 'use strict';

module.exports = {
    someconfig: {
        File: {
            path: '/some/path', // comment
            some_number: 50, // comment
        },
        Syslog: {
            path: '/some/other/path', // comment
            localhost: 'localhost', //comment
            app_name: 'somename', // comment
        },
        /**
        * some really
        * long
        * long
        * comment
        */
    },

    db: {
        mongoose: "5.0",
        servers: [{
            host: '1.1.1.1'
        }],
        database: 'somebase',  // comment
        user: 'myUserName',  //comment
        pass: 'myPassword',  // comment

        // some_comment

        config: {
            // some comment
            autoIndex: false
        },

        // lots of
        // comments
        // several lines

        someconfig: {
            // comment
            somevariable: "variable",
            anothervariable: "reallylongstring_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
        },
    },

    anotherconfig: {
        very_nested: [{
            host: '1.1.1.1'
        }],
        database: 'anotherdatabase', // comment
        user: 'myUserName', // comment
        pass: 'myPassword', // comment
    },


};
 

и код, который я пробовал, выглядит так

 from slimit.parser import Parser

parser = Parser()
tree = parser.parse(open(r'C:pathtoconfig copy.js').read())
fields = {getattr(node.left, 'value', ''): getattr(node.right, 'value', '')
          for node in nodevisitor.visit(tree)
          if isinstance(node, ast.Assign)}

print (fields)
 

Но он возвращает это

 {'': '', 'someconfig': '', 'File': '', 'path': "'/some/other/path'", 'some_number': '50', 'Syslog': '',
 'localhost': "'localhost'", 'app_name': "'somename'", 'db': '', 'mongoose': '"5.0"',
 'servers': '', 'host': "'1.1.1.1'", 'database': "'anotherdatabase'",
 'user': "'myUserName'", 'pass': "'myPassword'", 'config': '', 'autoIndex': 'false', 'somevariable': '"variable"',
 'anothervariable': '"reallylongstring_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"',
 'anotherconfig': '', 'very_nested': ''}
 

как вы можете видеть, он смешал два имени базы данных («какой-то базы» там нет, только «другая база»).

Есть идеи о том, как правильно получить мои данные? Спасибо 🙂

Ответ №1:

Я не думаю, что здесь полезно использовать NodeVisitor.

Вместо этого рекурсивно пройдитесь по всему AST и создайте вложенный дикт из всех интересующих элементов. В качестве минимальной реализации, которая будет расширена по вашему усмотрению:

 from slimit.parser import Parser
from slimit import ast

parser = Parser()

with open(r'config.js', encoding='utf8') as js:
    tree = parser.parse(js.read())

def walk(node):
    if not isinstance(node, ast.Node): 
        return
    elif isinstance(node, ast.Program):
        # the Program node itself becomes a dict of its contained assignments
        items = [walk(child) for child in node.children()]
        # assignments can be recognized by the fact that they are (name, value) tuples
        return dict(i[0] for i in items if isinstance(i[0], tuple))
    elif isinstance(node, ast.Assign):
        # an Assignment node becomes a (name, value) tuple
        return walk(node.left), walk(node.right)
    elif isinstance(node, ast.DotAccessor):
        # a DotAccessor node becomes a string.joined.with.dots
        return '.'.join(walk(child) for child in node.children())
    elif isinstance(node, ast.Object):
        # an Object node becomes a dict
        return dict(walk(child) for child in node.children())
    elif isinstance(node, ast.Array):
        # an Array node becomes a list
        return list(walk(child) for child in node.children())
    elif isinstance(node, (ast.Identifier, ast.String, ast.Number, ast.Boolean)):
        # Indentifiers and primitives give their native values
        return node.value       
    return [walk(child) for child in node.children()]

result = walk(tree)
print(result)
 

который создает аккуратно вложенный граф объектов, с которым должно быть легко работать:

 {
    'module.exports': {
        'someconfig': {
            'File': {
                'path': "'/some/path'",
                'some_number': '50'
            },
            'Syslog': {
                'path': "'/some/other/path'",
                'localhost': "'localhost'",
                'app_name': "'somename'"
            }
        },
        'db': {
            'mongoose': '"5.0"',
            'servers': [{'host': "'1.1.1.1'"}],
            'database': "'somebase'",
            'user': "'myUserName'",
            'pass': "'myPassword'",
            'config': {'autoIndex': 'false'},
            'someconfig': {
                'somevariable': '"variable"',
                'anothervariable': '"reallylongstring_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"'
            }
        },
        'anotherconfig': {
            'very_nested': [{'host': "'1.1.1.1'"}],
            'database': "'anotherdatabase'",
            'user': "'myUserName'",
            'pass': "'myPassword'"
        }
    }
}
 

Комментарии:

1. @Aude Это все еще немного грязно и менее элегантно, чем мне бы хотелось. Я постараюсь еще немного прибраться сегодня попозже. Кроме того, вероятно, есть способ превратить это в шаблон для посетителей, но простая рекурсия показалась мне самым быстрым подходом.

2. Я оставил комментарий, чтобы поблагодарить вас, который каким-то образом исчез. В любом случае, большое спасибо за вашу помощь, и он делает именно то, что я хотел. Если вам удастся сделать его еще чище, это будет впечатляюще, он и так выглядит великолепно, по крайней мере, для меня. Я не знал, что вы можете вызывать функцию внутри себя, как вы это делаете с walk: walk(child, context) это единственное, что кажется странным.

3. @Aude Это называется рекурсией. Да, предпринимаются определенные усилия по «очистке» комментариев, и комментарии типа «спасибо» часто сбиваются, вероятно, с помощью какого-то алгоритма, но это делают и модераторы. Я не могу сказать, что согласен с этим, но я мало что могу с этим поделать.

4. @Aude Я еще немного отполировал код, он добирается. Я уверен, что нам еще многое предстоит улучшить.

5. теперь все очень аккуратно. Мне нравится, как вы комментируете каждый шаг 🙂