将复杂的配置文件转换为 id,parent,key,value 格式

Convert complex config file to id,parent,key,value format

我正在尝试像下面的示例那样转换配置文件,这样我就可以生成 SQL 命令以插入到我的 Oracle table 中。我正在尝试使用 Perl,但我愿意尝试其他语言,例如 Python.

配置示例:

# This is a comment
feed_realtime_processor_pool = ( 11, 12 ) ;
dropout_detection_time_start = "17:00";
# Sometimes the config can have sub-structures
named_clients = (
{
  name = "thread1";
  user_threads = (
     { name = "realtime1"; cpu = 11; } # more comments
     { name = "realtime2"; cpu = 12; } # more comments
    );
}
);

(...)

正在转换(空格只是为了更好的可视化):

id,parent, key,                            value
01,null,   'feed_realtime_processor_pool', '11'
02,null,   'feed_realtime_processor_pool', '12'
03,null,   'dropout_detection_time_start', '17:00'
04,null,   'named_clients',                null
05,04,     'name',                         'thread1'
06,04,     'user_threads',                 null
07,06,     'name',                         'realtime1'
08,06,     'cpu',                          '11'
09,06,     'name',                         'realtime2'
10,06,     'cpu',                          '12'

所以,问题是:

  1. 有人知道一些 lib/module 可以做到这一点吗?我可以找到配置解析器,但找不到可以给我提供树格式的好配置解析器。

  2. 如果没有,有人可以给我一些关于我应该如何启动这个脚本的建议,也许使用不同的模块来帮助我解析配置?清理和解析配置对我来说是最难的部分。

第一次更新:

  1. 我忘了说,我有大量配置要导入到我的数据库中,这不是一次性事件。每当我公司的客户为不同的安装生成新配置时,此脚本都会 运行。
  2. 我选择 perl 是因为我认为它的执行速度可能比 Python 快。
  3. 我很熟悉SQL,但我不太擅长Perl
  4. 配置遵循我在示例中放置的相同结构,唯一的变化与 key/values 对之间的空格数或花括号的缩进等有关
  5. 我已经开始使用 DBI 编写 perl 脚本,但主要问题是将此配置解析为我可以轻松使用的格式。我总是发现我的正则表达式被破坏的情况,并且我不断地调整。如果我可以只使用一个库来自动解析配置,那就太好了。我要试试你们提到的那些。

谢谢!

第二次更新:

伙计们,我在 Perl Monks 上交叉 post 编辑了这个问题,并在那里得到了另一个反馈,我也在测试。一旦我测试了所有内容,post 会给出答案吗?谢谢

听起来你希望 Perl 分解配置文件并从中构建 SQL 语句,你可以这样做,但这样做也意味着创建 ID,如果数据库自动递增。

我建议学习如何使用 Perl 直接与数据库交互。该区域由两个模块控制:DBI and DBIx::Class.

这两个模块都提供了 create/read/update/delete 数据的方法,并且可以存储来自查询的 return 值,因此您可以在后续查询中使用 ID 和其他 table 数据。他们还对事务进行错误处理,这总是很好。

如果这是一次性脚本并且您已经熟悉 SQL,我推荐 DBI,因为它使用了很多普通的 SQL 并且可能会更快地学习.

如果您想要在 Perl 中使用数据库的 OO 解决方案,或者如果您打算进行大量数据库工作,那么我建议您花时间学习 DBIx::Class,因为您可以创建一个完整的更强大解决方案的架构模型。

无论哪种情况,您还需要 DBD::Oracle 模块,其中包含规则 DBI 和 DBIx::Class 需要与 Oracle 数据库交互。

更新:

我忘了提...如果您坚持将配置文件转换为 SQL 语句,您可以尝试 SQL::Abstract 将 Perl 数据结构转换为 SQL声明。

更新2:

我不确定是否有针对该特定配置语法的模块,但它看起来非常接近 JSON 的格式,因此您可以将文件转换为该格式,然后使用 JSON.

需要删除注释,数组从括号更改为方括号,等号更改为冒号,所有文本都需要用引号引起来,分号更改为逗号。我认为最棘手的部分是从每个块中删除最后一个逗号。

在删除尾随逗号时遇到困难,但这应该可以帮助您完成大部分工作:

my $config = <<'CONFIG';
# This is a comment
feed_realtime_processor_pool = ( 11, 12 ) ;
dropout_detection_time_start = "17:00";
# Sometimes the config can have sub-structures
named_clients = (
{
  name = "thread1";
  user_threads = (
     { name = "realtime1"; cpu = 11; } # more comments
     { name = "realtime2"; cpu = 12; } # more comments
    );
}
);
CONFIG

# Remove comments
$config =~ s{[#].+?$}{}mg;

# Convert Arrays
$config =~ s{\(}{[}mg;
$config =~ s{\)}{]}mg;

# Convert key-value seaprators
$config =~ s{=}{:}mg;

# Wrap text in quotes
$config =~ s{"?([\w\d]+)"?}{""}mg;
$config =~ s{"(\d\d?)":"(\d\d?)"}{":"}mg; # fix times

# Convert eol delimiters
$config =~ s{;}{,}mg;



# Wrap whole thing in brackets
$config = '{' . $config . '}';

print "$config\n\n";

撇开 Oracle,您也许可以对 Python 中的 "config" 文件做一些事情,因为它的语法即使不兼容也很相似(我不能告诉你)。但是我不知道会在 Perl 中对其进行评估的东西。相反,您可以尝试 Config::General 这将允许一个如下所示的配置文件:

feed_realtime_processor_pool 11
feed_realtime_processor_pool 12
dropout_detection_time_start 17:00
# Sometimes the config can have sub-structures
<named_client thread1>
  <user_threads realtime1>
     cpu 11
  </user_threads>
  <user_threads realtime2>
     cpu 12
  </user_threads>
</named_client>

# etc

然后阅读:

use Config::General;
$config = Config::General->new("my.conf"); 
my %config = $config->getall;

更简单的替代方案可能是 JSON 和 use JSON 或 XML::Simple 等。在 CPAN 中查看它们。这些将为您提供熟悉语法的树格式。

感谢大家的提示,但我认为最好的选择是遵循我在 perlmonks 站点上从 Choroba 收到的建议。我在这里复制他的回答:

“如果找不到解析配置格式的模块,请编写自己的解析器。Marpa::R2 可以帮助您完成任务:

#!/usr/bin/perl
use warnings;
use strict;
use feature qw{ say };

use Marpa::R2;

my $input = << '__INPUT__';
# This is a comment
feed_realtime_processor_pool = ( 11, 12 ) ;
dropout_detection_time_start = "17:00";
# Sometimes the config can have sub-structures
named_clients = (
{
  name = "thread1";
  user_threads = (
     { name = "realtime1"; cpu = 11; } # more comments
     { name = "realtime2"; cpu = 12; } # more comments
    );
}
);
__INPUT__

my $dsl = << '__DSL__';

lexeme default = latm => 1
:default ::= action => ::first

Config    ::= Elements
Elements  ::= Element+                              action => grep_def
+ined
Element   ::= (Comment)                             action => empty
            | Name (s eq s) Value                   action => [values]
            | Name (s eq s) Value (semicolon s)     action => [values]
Comment   ::= (hash nonnl nl)                       action => empty
Name      ::= alpha
Value     ::= List
            | String
            | Num
            | Struct
List      ::= (lpar) Nums (rpar s semicolon s)
Nums      ::= Num+  separator => comma              action => listify
Num       ::= (s) digits (s)
            | (s) digits
            | digits (s)
            | digits
String    ::= (qq) nqq (qq semicolon s)             action => quote
Struct    ::= (lpar s) InStructs (rpar semicolon s)
InStructs ::= InStruct+                             action => grep_def
+ined
InStruct  ::= (lcurl s) Elements (rcurl s)
            | (Comment s)                           action => empty
            | Element

s         ~ [\s]*
eq        ~ '='
hash      ~ '#'
nonnl     ~ [^\n]*
nl        ~ [\n]
alpha     ~ [a-z_]+
lpar      ~ '('
rpar      ~ ')'
lcurl     ~ '{'
rcurl     ~ '}'
semicolon ~ ';'
comma     ~ ','
digits    ~ [\d]+
qq        ~ '"'
nqq       ~ [^"]+

__DSL__

sub listify      { shift; [ @_ ] }
sub quote        { qq("$_[1]") }
sub empty        {}
sub grep_defined { shift; [ grep defined, @_ ] }


my $id = 1;
sub show {
    my ($parent, $name, $elems) = @_;
    if (ref $elems->[0]) {
        show($parent, $name, $_) for @$elems;
    } elsif (ref $elems->[1]) {
        if (ref $elems->[1][0]) {
            say join ', ', $id, $parent, $elems->[0], 'null';
            show($id++, $elems->[0], $elems->[1]);
        } else {
            for my $e (@{ $elems->[1] }) {
                say join ', ', $id++, $parent, $elems->[0], $e;
            }
        }
    } else {
        say join ', ', $id++, $parent, @$elems;
    }
}


my $grammar = 'Marpa::R2::Scanless::G'->new({ source => $dsl });
show('null', q(), ${ $grammar->parse($input, 'main') });
[download]
Output:

1, null, feed_realtime_processor_pool, 11
2, null, feed_realtime_processor_pool, 12
3, null, dropout_detection_time_start, "17:00"
4, null, named_clients, null
5, 4, name, "thread1"
6, 4, user_threads, null
7, 6, name, "realtime1"
8, 6, cpu, 11
9, 6, name, "realtime2"
10, 6, cpu, 12

请注意,要安装此模块,我们需要安装许多依赖项:

sudo cpan IPC::Cmd
sudo cpan Module::Build
sudo cpan Time::Piece
sudo cpan Marpa::R2