2

具有以下字符串:

commit a8c11fcee68881dfb86095aa36290fb304047cf1
log size 110
Author: XXXXXX XXXXXXXX <XXXXXXXXXXXXXXX@XXXXX.XXX>
Date:   Tue, 10 Apr 2012 11:19:44 +0300

    First commit

3       0       README.MD

如何使用110语法定义中的值来匹配其余的东西?“日志大小”包括字段(此处为:AuthorDate,但可以有任意数量的字段)和实际消息。

最后一行不是“日志消息”的一部分。

我想要得到的是 的值、带有和之commit类的元数据的字典,以及实际的日志消息,这里是“第一次提交”。AuthorDate

问题是,log size告诉我这条消息有多长,但这也包括Author字段Date

110是这个字符串的大小:

Author: XXXXXX XXXXXXXX <XXXXXXXXXXXXXXX@XXXXX.XXX>
Date:   Tue, 10 Apr 2012 11:19:44 +0300

    First commit
4

3 回答 3

2

我会分三个阶段来做:

  1. 使用正则表达式查找每个提交,并获取其 id 和日志大小。
  2. 使用步骤 1 中匹配的结尾和日志大小,我将从字符串中切出元数据+消息。
  3. 将步骤 2 中的字符串解析为字典+消息。

前两个步骤可以按如下方式完成:

In [25]: s = """commit a8c11fcee68881dfb86095aa36290fb304047cf1
log size 110
Author: XXXXXX XXXXXXXX <XXXXXXXXXXXXXXX@XXXXX.XXX>
Date:   Tue, 10 Apr 2012 11:19:44 +0300

    First commit
3       0       README.MD
"""

In [26]: m = re.search('commit (.*)\nlog size (.*)\n', s)

In [27]: s[m.end():m.end()+int(m.group(2))]
Out[27]: 'Author: XXXXXX XXXXXXXX <XXXXXXXXXXXXXXX@XXXXX.XXX>\nDate:   Tue, 10 Apr 2012 11:19:44 +0300\n\n    First commit\n'

如果调用了最后一个字符串step2,则可以按如下方式进行其余的解析:

In [48]: meta, msg = step2.split('\n\n', 1)

In [49]: dict([map(str.strip, line.split(':', 1)) for line in meta.split('\n')])
Out[49]: 
{'Author': 'XXXXXX XXXXXXXX <XXXXXXXXXXXXXXX@XXXXX.XXX>',
 'Date': 'Tue, 10 Apr 2012 11:19:44 +0300'}

In [50]: msg
Out[50]: '    First commit\n'
于 2012-12-01T11:36:11.857 回答
2

我对算法的想法与 NPE 相同。
但是我将正则表达式的使用推得更远了。

我已经用第二次出现的日志消息扩展了分析的文本,注意在“日志大小 xxx\n”行中放置正确数量的字符

正则表达式 1将每个出现分成 4 组。第三组包含具有字典的行,第四组具有字典行之后和其他出现之前的尾随行。

import re

ss = """commit a8c11fcee68881dfb86095aa36290fb304047cf1
log size 110
Author: XXXXXX XXXXXXXX <XXXXXXXXXXXXXXX@XXXXX.XXX>
Date:   Tue, 10 Apr 2012 11:19:44 +0300

    First commit
3       0       README.MD
blablah bla
commit 12458777AFDRE1254
log size 170
   Author: Jim Bluefish <jimblfsh@gmail.com>
Date   :   Yesterday 21:45:01 +0800
  A key with whitespace :       A_stupid_value    

    Funny commit
  From far from you
457      popo       not_README.MD"""

n = 0
print ('------ DISPLAY OF THE TEXT ------\n'
       ' col 1: index of line,\n'
       ' col 2: number of chars in the line\n'
       ' col 3: total of the numbers of chars of lines\n'
       ' col 4: repr(line)\n')
for j,line in enumerate(ss.splitlines(1)):
    n += len(line)
    print '%2d  %2d  %3d  %r' % (j,len(line),n,line)


print '=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-='
print '\n\n\n------ ANALYSER 2 OF THE TEXT ------'

regx1 = re.compile('^commit +(.+) *\r?\n'
                   'log size +(\d+) *\r?\n'
                   '((?:^ *.+?(?<! ) *: *.+(?<! ) *\r?\n)+)'
                   '((?:.*\r?\n(?!commit))+)',
                   re.MULTILINE)

regx2 = re.compile('^ *(.+?)(?<! ) *: *(.+)(?<! ) *\r?\n',
                   re.MULTILINE)

for mat in regx1.finditer(ss):

    commit_value,logsize,dicolines,msg = mat.groups()

    print ('\ncommit_value == %s\n'
           'logsize == %s'
           % (commit_value,logsize))

    print 'dictionary :\n',dict(regx2.findall(dicolines))

    actual_log_message = msg[0:int(logsize)-len(dicolines)].strip(' \r\n')
    print 'actual_log_message ==',repr(actual_log_message)

结果

------ DISPLAY OF THE TEXT ------
 col 1: index of line,
 col 2: number of chars in the line
 col 3: total of the numbers of chars of lines
 col 4: repr(line)

 0  48   48  'commit a8c11fcee68881dfb86095aa36290fb304047cf1\n'
 1  13   61  'log size 110\n'
 2  52  113  'Author: XXXXXX XXXXXXXX <XXXXXXXXXXXXXXX@XXXXX.XXX>\n'
 3  40  153  'Date:   Tue, 10 Apr 2012 11:19:44 +0300\n'
 4   1  154  '\n'
 5  17  171  '    First commit\n'
 6  26  197  '3       0       README.MD\n'
 7  12  209  'blablah bla\n'
 8  25  234  'commit 12458777AFDRE1254\n'
 9  13  247  'log size 170\n'
10  45  292  '   Author: Jim Bluefish <jimblfsh@gmail.com>\n'
11  36  328  'Date   :   Yesterday 21:45:01 +0800\n'
12  51  379  '  A key with whitespace :       A_stupid_value    \n'
13   1  380  '\n'
14  17  397  '    Funny commit\n'
15  20  417  '  From far from you\n'
16  33  450  '457      popo       not_README.MD'



------ ANALYSER OF THE TEXT ------

commit_value == a8c11fcee68881dfb86095aa36290fb304047cf1
logsize == 110
dico :
{'Date': 'Tue, 10 Apr 2012 11:19:44 +0300', 'Author': 'XXXXXX XXXXXXXX <XXXXXXXXXXXXXXX@XXXXX.XXX>'}
actual_log_message == 'First commit'


commit_value == 12458777AFDRE1254
logsize == 170
dico :
{'Date': 'Yesterday 21:45:01 +0800', 'A key with whitespace': 'A_stupid_value', 'Author': 'Jim Bluefish <jimblfsh@gmail.com>'}
actual_log_message == 'Funny commit\n  From far from you'
于 2012-12-01T13:50:07.693 回答
0

您使用“pyparsing”标签标记了您的问题,因此您可以使用 pyparsing 来解决它。Pyparsing 包含一个名为的辅助方法countedArray,它几乎可以满足您的需求。你可以想到:

countedArray(something)

作为快捷方式:

integer + something*(whatever was parsed as the integer)

并将解析的数据作为某些东西的列表返回。

source = """commit a8c11fcee68881dfb86095aa36290fb304047cf1
log size 110
Author: XXXXXX XXXXXXXX <XXXXXXXXXXXXXXX@XXXXX.XXX>
Date:   Tue, 10 Apr 2012 11:19:44 +0300

    First commit<not in the message, more than 110 chars>

3       0       README.MD
"""

from pyparsing import *
any_char = Word(printables+" \n",exact=1).leaveWhitespace()

log_message = countedArray(any_char)
# we want a string, not an array of single characters
log_message.setParseAction(lambda t: ''.join(t[0]))

entry = "commit" + Word(hexnums)('id') + "log size" + log_message

msg = entry.parseString(source)[-1]
print (msg)

给出:

Author: XXXXXX XXXXXXXX <XXXXXXXXXXXXXXX@XXXXX.XXX>
Date:   Tue, 10 Apr 2012 11:19:44 +0300

    First commit

您可以看到我们已经阅读了但不包括“不在消息中...”部分(我将其添加到您的源字符串中以显示countedArray正确停止在第 110 个字符处)。我添加了一个解析操作,当表达式匹配时,pyparsing 将作为解析时回调运行。匹配的标记被传递给解析操作,如果操作返回一个值,该值将替换输出中的已解析标记。在这里,我们使用一个简单的 lambda 来获取第一个标记(字符数组),并将它们连接成一个字符串。

但是您也说过要提取“作者”和“日期”字段。它们实际上是在 log_message 中提取的内容的一部分,因此您必须将该字符串传递给另一个表达式。幸运的是,您可以在第二个解析操作中执行此类操作。

在这个第二阶段解析中,我决定创建一个解析器,它将采用以下形式的任何键值:

some key: the value of that key up to the end of the line

如果“作者”和“日期”只是您可能在源文本中找到的任意数量的键的示例。Pyparsing 也有命名结果,类似于正则表达式中的命名组。通常,当名称已知时,您只需在上面显示的名称上加上您的提交 ID。但是在您的日志消息中,实际名称是从输入本身解析出来的,因此为此,pyparsing 有一个Dict类。该类Dict将采用一组已解析的组,并用每个组的名称装饰该数据,将组的第一个元素作为名称,将组的其余部分作为值。因此,我们希望显示每个键值的名称,其中 ':' 之前的所有内容都将是名称,跳过冒号和任何前导空格,然后将行的其余部分作为值。

COLON = Suppress(':')
keyed_value = Group(Word(printables+' ',excludeChars=':') + COLON + empty + restOfLine)
keyed_entries = Dict(ZeroOrMore(keyed_value))

我们仍然想要该日志消息,最简单的是使用 pyparsingSkipTo将其他所有内容都带到字符串的末尾:

everything_else_up_to_the_end_of_the_string = SkipTo(StringEnd())

这是您的日志消息正文的语法:

log_message_body = keyed_entries + 
                    everything_else_up_to_the_end_of_the_string('message')

我们已经有了一个解析动作,可以将 log_message 中的所有解析字符组合成一个字符串,但是可以将多个解析动作链接到一个表达式上。我们将添加第二个解析操作,它将使用 log_message_body 语法解析解析的 log_message:

def parseMessage(tokens):
    return log_message_body.parseString(tokens[0])
log_message.addParseAction(parseMessage)

现在我们可以再次运行完整的解析器,这一次,输出结果和它们的名字。可以像访问对象的属性一样访问命名的结果(或者如果您愿意,可以使用 dict 表示法):

log_entry = entry.parseString(source)
print (log_entry.id)
print (log_entry['message'])
print (log_entry.dump())

给出:

a8c11fcee68881dfb86095aa36290fb304047cf1
First commit
['commit', 'a8c11fcee68881dfb86095aa36290fb304047cf1', '
    log size', ['Author', 'XXXXXX XXXXXXXX <XXXXXXXXXXXXXXX@XXXXX.XXX>'], 
    ['Date', 'Tue, 10 Apr 2012 11:19:44 +0300'], 
    'First commit']
- Author: XXXXXX XXXXXXXX <XXXXXXXXXXXXXXX@XXXXX.XXX>
- Date: Tue, 10 Apr 2012 11:19:44 +0300
- id: a8c11fcee68881dfb86095aa36290fb304047cf1
- message: First commit
于 2012-12-07T09:57:52.717 回答