Ruby & Lmstat:parslet 和结构化多行块:换行语句放在哪里?
Ruby & Lmstat : parslet and structured multi-line block : where to put the newline statement?
我有一个 Flexlm/Flexnet 许可服务,我想解析该服务的输出。所有输出都是多行的结构化块。我的第一步是解析 lmutil lmstat -c <port@server> -a
的输出以使用许可证和增量。
我尝试使用 Ruby 和 Parslet。所有行都被单独解析。我有一个规则来解析特定类型行的重复,但我无法解析结构化的行块。
我正在寻找定义在多行结构化块中放置 'newline' 语句的位置的法则(在这种情况下最好的词是 'rule')。
我使用 Debian Jessie (stable/x86_64) 和 Ruby 2.1.5p273 和 Parslet 1.6.1-1。
我联系了作者,他很抱歉,但他没有足够的时间来帮助我。看到的网页是:
- URL: www.viget.com/articles/write-you-a-parser-for-fun-and-win
- URL: jmettraux.wordpress.com/2011/05/11/parslet-and-json/
- 递归下降解析器 (Calle) - 视频
- Wicked Good Ruby 2013 - Writing DSL's with Parslet by Jason Garber - Video
- 包含块的多行方法链接的正确 Ruby 样式 - Whosebug
- Ruby parslet:解析多行 - Whosebug
- 如何使用 Parslet 在 Ruby 中处理 C 风格的注释? - Whosebug
我花了很多时间试图理解如何构建多行结构化块的规则。下面是我的源代码,其中包含所有测试字符串和输出。
我的方法是构建:
- 解析行片段的基本规则
- 解析没有'newline'语句的完整行的规则;
- 一个规则来解析重复的相同类型的信息,比如使用的标记行;
- 解析一组数据的规则:表头+重复行;
- 解析重复组的规则。
我不确定第 3 点,我完全迷失了“4”和“5”。
在此先感谢您的帮助。 [ 2017 年 7 月 14 日:部分代码已删除]
#!/usr/bin/env ruby
# This code try to parse the output of 'lmutil lmstat -c <port@server> -a'.
require 'parslet'
require 'parslet/convenience'
require 'pp'
### Begin of the class Lmstat
class Lmstat < Parslet::Parser
###
# Small parts to parse
rule(:digit) { match(/\d/).repeat(1) }
rule(:space) { str(' ').repeat }
rule(:eof) { any.absent? }
rule(:blank_line) { space.maybe >> newline >> space.maybe }
rule(:newline) { str("\r").maybe >> str("\n") }
rule(:txt) { match(/[\w\d\s,_.'",-:]/).repeat }
def parenthese( atom, qte='()' )
if (qte == '()' )
str('(') >> atom >> str(')')
else
str(qte) >> atom >> str(qte)
end
end
###
###
# The header is not parsed for the moment, while I can't
rule (:header) do
# Not define until the other parts are OK.
end
rule(:feature_line) do
feature_usage.as(:feature_line) >> # newline >>
feature_line_id.as(:feature_line_id).repeat.as(:f_line)
end
rule(:feature_line_id) do
feature_version >> newline >> feature_type >> newline >>
feature_user_group >> newline
end
rule(:feature_line_id_group) do
(newline >> feature_line_id).repeat(1).as(:f_line_group) >> newline
end
rule(:feature_usage) do
str("Users of ") >> feature.as(:feature_usage) >> str(':') >> space >>
parenthese( feature_used ) >> space.maybe
end
rule(:feature) { match(/[\w_-]/).repeat }
# Total of 1 license issued; Total of 0 licenses in use
rule(:feature_used) do
feature_token.as(:feature_token_issued) >>
feature_token.as(:feature_token_used) >> space.maybe >> newline.maybe
end
# (Total of 1 license issued; Total of 0 licenses in use)
rule(:feature_token) do
space.maybe >> str('Total of ') >> digit.repeat.as(:feature_token_value) >>
space >> license >> issued_used >>
str(';').maybe >> space.maybe
end
rule(:license) { str('license') >> str('s').maybe >> space }
rule(:issued_used) do
str('issued') | str('in use')
end
# v2015.1231
rule(:version) { match(/[\w\d.-]/).repeat }
# "incr-1"
rule(:vendor) { match(/[\w-]/).repeat }
# "incr-1" v2015.1231, vendor: ansoftd
rule(:feature_version) do
# newline >>
space.maybe >> parenthese( feature.as(:feature), '"' ) >>
space >> version.as(:version) >> str(', vendor: ') >>
vendor.as(:vendor) >> space.maybe >>
str(', expiry: ').maybe >> match(/[\w\d-]/).repeat.as(:expiration).maybe
end
# floating license
# nodelocked license, locked to "ID=12345"
rule(:feature_type) do
space.maybe >>
( (space.maybe >> str("floating license").as(:floating) >> space.maybe) |
(space.maybe >> str('nodelocked license, locked to "ID=') >>
digit.as(:license_id) >> str('"') >> space.maybe)).as(:feature_type) >>
space.maybe
end
# \t 28 RESERVATIONs for GROUP Better_Group (server/27000)
rule(:reserve) do
space.maybe >> str("\t").maybe >> digit.as(:reserve_value) >>
str(" RESERVATION") >> str("s").maybe >> str(" for ") >>
word.as(:reserve_type) >> space >> word.as(:reserve_who) >>
space >>
parenthese( host.as(:server) >> str("/") >> digit.as(:port) )
end
rule(:reserve_group) do
(newline >> reserve).repeat(1).as(:reservation)
end
rule(:feature_user) do
space.maybe >>
word.as(:login) >> space >> host.as(:host_user) >> space >> host.as(:id) >>
space >> parenthese( version.as(:version) ) >> space >> port >> date_queue
end
rule(:feature_user_group) do
(newline >> feature_user).repeat(1).as(:feature_user_group)
end
# queued for 1 license
rule(:queue) do
str('queued for ') >> digit.as(:queued) >> str(' license') >> str('s').maybe
end
rule(:date_queue) do
( ( str(',') >> space >> date >> cmt.as(:comment)) | (space >> queue) )
end
rule(:cmt) do
space.maybe >> match(/[^\r\n]/).repeat#.as(:cmt)
end
rule(:word) { match(/[\w\d-]/).repeat }
rule(:host) { match(/[\w\d_.-]/).repeat }
rule(:port) do
parenthese( host.as(:server) >> str('/') >> digit.as(:server_port) >>
space >> digit.as(:vendor_port) )
end
rule(:date) do
str('start ') >> word.as(:date_dayname) >> space >>
digit.as(:date_month) >> str('/') >> digit.as(:date_day) >> space >>
digit.as(:date_hour) >> str(':') >> digit.as(:date_minute)
end
end
### End of the class Lmstat
###
# Some multiline tests case.
t_feature_line_id = %q{ "incr-2" v9999.9999, vendor: vendor-daemon
floating license
henry abc057 abc057 (v2015.0623) (shoe/28512 3886) queued for 1 license
simon abc057 abc057 (v2014.1110) (shoe/28512 4166), start Fri 11/20 15:37, 10 licenses
simon abc057 abc057 (v2014.1110) (shoe/28512 4166), start Fri 11/20 15:37 queued for 1 license
}
t_feature_line_id_group = %q{ "incr-2" v9999.9999, vendor: vendor-daemon
floating license
henry abc057 abc057 (v2015.0623) (shoe/28512 3886) queued for 1 license
jason abc057 abc057 (v2015.0623) (shoe/28512 3886), start Fri 11/20 14:41
simon abc057 abc057 (v2014.1110) (shoe/28512 4166), start Fri 11/20 15:37, 10 licenses
simon abc057 abc057 (v2014.1110) (shoe/28512 4166), start Fri 11/20 15:37 queued for 1 license
"inc2" v9999.9999, vendor: inc2vendor
floating license
simon abc057 abc057 (v2014.1110) (shoe/28512 4166), start Fri 11/20 15:37, 10 licenses
simon abc057 abc057 (v2014.1110) (shoe/28512 4166), start Fri 11/20 15:37 queued for 1 license }
t_feature_line = %q{Users of ansys: (Total of 9 licenses issued; Total of 6 licenses in use)
"incr-2" v9999.9999, vendor: vendor-daemon
floating license
jason abc057 abc057 (v2015.0623) (shoe/28512 3886), start Fri 11/20 14:41
simon abc057 abc057 (v2014.1110) (shoe/28512 4166), start Fri 11/20 15:37, 10 licenses
simon abc057 abc057 (v2014.1110) (shoe/28512 4166), start Fri 11/20 15:3}
t_feature_line_group = %q{
"incr-2" v9999.9999, vendor: vendor-daemon
floating license
jason abc057 abc057 (v2015.0623) (shoe/28512 3886), start Fri 11/20 14:41
simon abc057 abc057 (v2014.1110) (shoe/28512 4166), start Fri 11/20 15:37, 10 licenses
simon abc057 abc057 (v2014.1110) (shoe/28512 4166), start Fri 11/20 15:37 queued for 1 license
"incr-2" v9999.9999, vendor: vendor-daemon
floating license
jason abc057 abc057 (v2015.0623) (shoe/28512 3886), start Fri 11/20 14:41
simon abc057 abc057 (v2014.1110) (shoe/28512 4166), start Fri 11/20 15:37, 10 licenses
simon abc057 abc057 (v2014.1110) (shoe/28512 4166), start Fri 11/20 15:37}
t_feature_user= %q{jason abc057 abc057 (v2015.0623) (shoe/28512 3886), start Fri 11/20 14:41}
t_feature_group = %q{ "incr-2" v9999.9999, vendor: vendor-daemon
floating license
jason abc057 abc057 (v2015.0623) (shoe/28512 3886), start Fri 11/20 14:41
simon abc057 abc057 (v2014.1110) (shoe/28512 4166), start Fri 11/20 15:37, 10 licenses
jessica abc057 abc057 (v2014.1110) (shoe/28512 4166), start Fri 11/20 15:37
"MATLAB" v35, vendor: MLM, expiry: 01-jan-0000
nodelocked license, locked to "ID=12345"
albert node7563 node7563 (v34) (shoe/27000 201), start Mon 5/23 6:16 (linger: 1235700)
victoria abc087 /dev/pts/1 (v29) (shoe/27000 3401), start Mon 5/23 6:30}
###
###
# Method to test the parsing.
def parse_method(method,str)
lmstat = Lmstat.new
unless lmstat.respond_to?(method)
raise ArgumentError,
"\n\n\t***** ERROR: Unknown method -> '#{method}' ******\n\n",
caller[1..-1]
end
begin
m = "lmstat.#{method}.parse('"+ str + '\')'
puts "=> Test of #{m}"
eval (m)
rescue Parslet::ParseFailed => failure
puts failure.cause.ascii_tree
end
end
###
###
# Not called if 'irb' is used to load the program.
if __FILE__ == $PROGRAM_NAME
puts "\n ###### Multilines #####"
parse_method('feature_user_group',t_feature_user_group)
parse_method("feature_line_id",t_feature_line_id)
pp parse_method("feature_line_id_group",t_feature_line_id_group)
end
输出[ 07/13/2017 : 删除以放置功能版本]
[更新 - 2017 年 4 月 29 日 - 问题已解决]
感谢 Nigel Thorne 的回答,它解决了我的问题。我已根据您的建议更正了 'space' 的规则。
[ 2014 年 7 月 13 日:删除一些文本以放置功能齐全的版本。 ]
[更新 - 2017 年 7 月 13 日 - 申请测试解析]
我已经完成了一个应用程序,用于测试使用 Ruby 和 Parslet 解析 lmstat 的输出。由于解析依赖于每个编辑器,有些情况可能无法涵盖,但使用了 30 多个许可服务来验证解析。
我可以给3个文件:
- parse_lmstat.rb : 使用 './parse_lmstat --help' 获得帮助。测试解析的应用程序。
- readstdin_lmstat.rb :从 STDIN 读取以 YAML 格式生成的经过解析的 lmstat 输出。
- display_lmstat.rb :显示如何访问数据,它用于改进解析的结构。该脚本比
irb
会话更好。它从 STDIN 读取具有 YAML 格式的已解析 lmstat 输出。
一个例子:
~/bin/lmutil lmstat -a -c 1234@licserver | ./parse_lmstat.rb --screen | ./display_lmstat.rb
一个已知错误:当 [CTRL-C] 完成时,信号似乎没有被很好地捕获,Ruby 在某些情况下会发送一些错误消息。
现在,我梦想有一个小型 WEB 应用程序(SINATRA?)到 select 许可证服务器并显示数据,但我不会说 HTML 或 CSS ...任何帮助将不胜感激;-)
您将在下面找到 只有 类 来解析和转换 lmstat 的输出,因为限制为 30000 个字符。
#!/usr/bin/env ruby
#
# class_lmstat.rb
#
# This code try to parse the output of 'lmutil lmstat -c <port@server> -a'.
#
# Scapin - 11/07/2017
#
# For the Whosebug forums
#
require 'parslet'
require 'parslet/convenience'
require 'open3'
### Begin of the class Lmstat
class Lmstat < Parslet::Parser
###
# Small parts to parse
rule(:digit) { match(/\d/).repeat(1) }
rule(:space) { str(' ').repeat(1) }
rule(:eof) { any.absent? }
rule(:blank_line) { space.maybe >> newline >> space.maybe }
rule(:newline) { str("\r").maybe >> str("\n") }
rule(:txt) { match(/[\w_.\)\('\t ",-:\]/).repeat }
rule(:word) { match(/[\w-]/).repeat }
rule(:host) { match(/[\w_\.-]/).repeat }
rule(:cnx_id) { match(/[\/\w_.:]/).repeat }
rule(:cmt) { space.maybe >> match(/[^\r\n]/).repeat }
rule(:error_code) { match(/[,\d-]/).repeat }
def parenthese( atom, qte='()' )
if (qte == '()' )
str('(') >> atom >> str(')')
else
str(qte) >> atom >> str(qte)
end
end
###
root(:lmstat)
rule(:lmstat) do
(header.as(:header) >> body.repeat.as(:service) >> newline).as(:lmstat)
end
###
# The header is not parsed for the moment, while I can't
# handle the multiline block correctly.
#
# lmutil - Copyright (c) 1989-2013 Flexera Software LLC. All Rights Reserved.
# Flexible License Manager status on Fri 11/20/2015 16:39
#
# License server status: 1141@lic-server
# License file(s) on lic-server: /opt/license/soft/vendor1.lic:/opt/license/soft/vendor2.lic:
#
# lic-server: license server UP (MASTER) v11.13
#
# Vendor daemon status (on lic-server):
#
# vendor-daemon: UP v11.13
# Feature usage info:
#
###
rule (:header) do
copyright >> status_date >> newline >>
server >> license_file >> newline >>
server_status >> newline >>
vendor_daemon_status >> newline
end
rule (:body) do
(vendor_daemon.as(:vendor_daemon) >> feature_info.maybe >> newline >>
feature_line.repeat.maybe.as(:features))
end
# lmutil - Copyright (c) 1989-2013 Flexera Software LLC. All Rights Reserved.
rule (:copyright) do
space.maybe >> (str("lmutil - Copyright ") >> match(/./).repeat).as(:copyright) >> newline
end
# Flexible License Manager status on Fri 11/20/2015 16:39
rule(:status_date) do
space.maybe >> str("Flexible License Manager status on ") >>
word.as(:status_dayname) >> space >>
digit.as(:status_month) >> str("/") >> digit.as(:status_day) >>
str("/") >> digit.as(:status_year) >>
str(" ") >> digit.as(:status_hour) >>
str(":") >> digit.as(:status_min) >> newline
end
rule(:server) do
str("License server status: ") >>
digit.as(:server_port1) >> str("@") >> host.as(:server1) >>
( str(",") >> digit.as(:server_port2) >> str("@") >> host.as(:server2) >>
str(",") >> digit.as(:server_port3) >> str("@") >> host.as(:server3) ).maybe >>
newline
end
# License file(s) on lic-server: /opt/license/soft/licfile-1.lic:/opt/soft/licfile-2.lic:
rule(:license_file) do
space.maybe >> str("License file(s) on ") >>
match(/[\w\d._-]/).repeat.as(:license_files_server) >>
str(": ") >> txt.as(:license_files_names) >> newline
end
rule(:server_status) do
(space.maybe >> host.as(:server_host) >> str(": ") >>
( server_up | server_down) >> newline).repeat(0).as(:server_list)
end
rule(:server_up) do
str("license server ")>>str("UP").as(:server_up)>>
server_pos.maybe >> str(" ") >> cmt.as(:server_version)
end
rule(:server_pos) do
space >> parenthese( match(/[A-Za-z]/).repeat.as(:server_role))
end
# licserver: Cannot connect to license server system. (-15,570:115 "Operation now in progress")
rule(:server_down) do
space.maybe >> str("Cannot connect to license server system").as(:server_down) >>
str(". ") >> cmt.as(:server_error)
end
rule(:vendor_daemon_status) do
str("Vendor daemon status (on ") >> host.as(:server_daemon) >>
str("):") >> space.maybe >> newline
end
rule(:vendor_daemon) do
( vendor_daemon_up | vendor_daemon_down )
end
rule(:vendor_daemon_up) do
space.maybe >> word.as(:daemon) >> str(": ") >> word.as(:daemon_status) >>
space >> host.as(:daemon_version) >> newline
end
rule(:vendor_daemon_down_ini) do
space.maybe >> word.as(:daemon) >> str(": The desired vendor daemon is down. ") >>
parenthese( error_code.as(:daemon_status) ) >> space.maybe >> newline
end
# \n\n dconcept: No socket connection to license server manager. (-7,96)
rule(:vendor_daemon_down) do
space.maybe >> word.as(:daemon) >> str(": The desired vendor daemon is down. ") >>
parenthese( error_code.as(:daemon_status) ) >> space.maybe >> newline
space.maybe >> word.as(:vendor_daemon_down_msg_feature).maybe >>
str(': No socket connection to license server manager.').maybe >> space.maybe >>
cmt.as(:vendor_daemon_down_msg).maybe >> newline.maybe
end
rule(:feature_info) do
space.maybe >> str("Feature usage info:") >> space.maybe >> newline
end
###
# Users of soft_a: (Total of 1 license issued; Total of 0 licenses in use)
#
# "incr-1" v2015.1231, vendor: soft_ad
# floating license
#
# 28 RESERVATIONs for GROUP Better_Group (server/27000)
# 1 RESERVATION for USER toni (server/27000)
# scott abc056 abc056 (v2015.0623) (shoe/28512 3644), start Fri 11/20 15:45, 2 licenses
# scott abc056 abc056 (v2015.0623) (shoe/28512 4669), start Fri 11/20 15:45, 10 licenses
rule(:feature_line) do
feature_usage.as(:feature_line) >> newline >>
feature_line_id.repeat(0).as(:feature_line_id)
end
# "incr-1" v2015.1231, vendor: soft_ad
# floating license
#
# scott abc056 abc056 (v2015.0623) (shoe/28512 3644), start Fri 11/20 15:45, 2 licenses
# scott abc056 abc056 (v2015.0623) (shoe/28512 4669), start Fri 11/20 15:45, 10 licenses
rule(:feature_line_id) do
feature_version >> newline >> feature_type >> newline >>
# ( reserve.as(:reservation) | feature_user.as(:user)).repeat(1).as(:users) >> newline
( reserve.as(:reservation) | feature_user.as(:user)).repeat(1).as(:who) >> newline
end
# Users of soft_a: (Total of 1 license issued; Total of 0 licenses in use)
# Users of SOFT_B: (Uncounted, node-locked)
# Users of soft_c: (Error: 6 licenses, unsupported by licensed server)
rule(:feature_usage) do
str("Users of ") >> feature.as(:feature_name) >> str(':') >> space >>
parenthese( feature_used ) >> space.maybe >> newline
end
# Total of 1 license issued; Total of 0 licenses in use
# Uncounted, node-locked
rule(:feature_used) do
( ( feature_token.as(:feature_token_issued) >> feature_token.as(:feature_token_used)) |
( word.as(:feature_token_issued) >> str(', ') >> word.as(:feature_token_used) ) |
( str('Error: ') >> digit.repeat.as(:feature_token_error) >> space >> str( 'license') >>
str('s').maybe >> str(', ') >>
match(/[\w_', :-]/).repeat.as(:feature_token_error_cause) ) ) >>
space.maybe >> newline.maybe
end
# (Total of 1 license issued; Total of 0 licenses in use)
rule(:feature_token) do
space.maybe >> str('Total of ') >> digit.repeat.as(:feature_token_value) >>
space >> license >> issued_used >>
str(';').maybe >> space.maybe
end
rule(:license) { str('license') >> str('s').maybe >> space }
rule(:issued_used) do
str('issued') | str('in use')
end
# v2015.1231
rule(:version) { match(/[\w\d.-]/).repeat }
# "incr-1"
rule(:vendor) { match(/[\w-]/).repeat }
rule(:feature) { match(/[\w\d\/_+-]/).repeat }
# "incr-1" v2015.1231, vendor: soft_ad
rule(:feature_version) do
# newline >>
space >> parenthese( feature.as(:feature), '"' ) >>
space >> version.as(:version) >> str(', vendor: ') >>
vendor.as(:vendor) >> space.maybe >>
str(', expiry: ').maybe >> match(/[\w\d-]/).repeat.as(:expiration).maybe
end
rule(:feature_type) do
space >> ( float_type | node_type ).as(:feature_type)
end
# floating license
rule(:float_type) do
str("floating license").as(:floating) >> cmt.maybe >> newline
end
# nodelocked license, locked to "ID=654321"
# nodelocked license locked to NOTHING (hostid=ANY)
# uncounted nodelocked license locked to NOTHING (hostid=ANY)
# uncounted nodelocked license, locked to Vendor-defined "PTC_HOSTID=01-0A-01-0A-01"
rule(:node_type) do
str('uncounted ').maybe >> str("nodelocked license") >> str(',').maybe >> str(' locked to ').maybe >>
( ( str('"ID=') >> digit.as(:nodelocked_id) >> str('"') ) |
( host.as(:nodelocked_to) >> space >> parenthese(str('hostid=') >> host.as(:nodelocked_hostid)) ) |
( host.as(:nodelocked_to) >> space >>
parenthese(match(/[\w:_=' -]/).repeat.as(:nodelocked_hostid), '"') ) ) >>
space.maybe >> newline
end
# \t 28 RESERVATIONs for GROUP Better_Group (server/27000)
rule(:reserve) do
space.maybe >> str("\t").maybe >> digit.as(:reserve_value) >>
str(" RESERVATION") >> str("s").maybe >> str(" for ") >>
word.as(:reserve_type) >> space >> word.as(:reserve_who) >>
space >>
parenthese( host.as(:server) >> str("/") >> digit.as(:port) ) >>
newline
end
rule(:feature_user) do
(u_std | u_aselta | u_ans | u_c1 | u_c2 )
end
# scott abc056 abc056 (v2015.0623) (shoe/28512 3644), start Fri 11/20 15:45, 2 licenses
# albert node7563 node7563 (v34) (shoe/27000 201), start Mon 5/23 6:16 (linger: 1235700)
# hector node088 dev/tty (v2015.0312) (licserver/1446 3730), start Thu 11/19 9:08
# will pim.my.domain.org pim.my.domain.org 6656 (v2016.1129) (licserver/1446 2216), start Fri 5/12 14:51
rule(:u_std) do
space >> word.as(:login) >> space >> host.as(:host_user) >> space >>
cnx_id.as(:host_id) >>
space >> parenthese( version.as(:version) ) >> space >> port >> date_queue >>
newline
end
# scott cat :0 Token Lic (v7.000) (shoe/5300 15434), start Thu 7/6 17:07
rule(:u_c1) do
space >> word.as(:login) >> space >> host.as(:host_user) >> space >>
cnx_id.as(:host_id) >> space >> match(/[^(]/).repeat.as(:common_name) >>
parenthese( version.as(:version) ) >> space >> port >> date_queue >>
newline
end
# jessie cat bird:1144.0 APS Multi-core (Max. 16 cores) (v11.100) (licserver/5303 13188), start Thu 7/6 15:22, 4 licenses
rule(:u_c2) do
space >> word.as(:login) >> space >> host.as(:host_user) >> space >>
cnx_id.as(:host_id) >> space >> match(/[^(]/).repeat.as(:common_name) >>
str('(') >> match(/[^)]/).repeat.as(:common_info) >> str(')') >> space >>
parenthese( version.as(:version) ) >> space >> port >> date_queue >>
newline
end
# tiger pam.my.domain.org pam.my.domain.org 6656 (v2016.1129) (licserver/1446 2216), start Fri 5/12 14:51
rule(:u_ans) do
space >> word.as(:login) >> space >> host.as(:host_user) >> space >>
cnx_id.as(:host_id) >> ( space >> host.as(:further) ).maybe >>
space >> parenthese( version.as(:version) ) >> space >> port >> date_queue >>
newline
end
# clark node07 SOMETHING Inscale / grid (worker) (v1.0) (licserv01/27016 5506), start Fri 4/28 13:42, 4 licenses
# bunny orca SOMETHING Inscale / graphical (v1.0) (licserv01/27016 650), start Thu 4/13 10:27
rule(:u_aselta) do
space >> word.as(:login) >> space >> host.as(:host_user) >> space >>
word.as(:daemon) >> space >> word.as(:soft) >> str(" / ") >>
word.as(:function) >> (space >> parenthese( word.as(:tools) )).maybe >>
space >> parenthese( version.as(:version) ) >> space >> port >> date_queue >>
newline
end
# queued for 1 license
rule(:queue) do
str('queued for ') >> digit.as(:queued) >> str(' license') >> str('s').maybe
end
rule(:lic) do
str(',') >> space >> digit.as(:licenses) >> str(' license') >> str('s').maybe
end
rule(:date_queue) do
( ( str(',') >> space >> date >> ( lic | cmt.as(:comment))) | (space >> queue) )
end
rule(:port) do
parenthese( host.as(:server) >> str('/') >> digit.as(:server_port) >>
space >> digit.as(:vendor_port) )
end
rule(:date) do
str('start ') >> word.as(:date_dayname) >> space >>
digit.as(:date_month) >> str('/') >> digit.as(:date_day) >> space >>
digit.as(:date_hour) >> str(':') >> digit.as(:date_minute)
end
end
### End of the class Lmstat
### Begin of the class Trans
class Trans < Parslet::Transform
rule(:feature_token_value => simple(:v)) { Integer(v) }
rule(:user => subtree(:t)) do
if ( t.has_key?(:date_month) )
cal = { "Sun"=>"Dimanche", "Mon"=>"Lundi", "Tue"=>"Mardi",
"Wed"=>"Mercredi", "Thu"=>"Jeudi", "Fri"=>"Vendredi"}
clock = Time.now
# Addition of keys
t.merge!( { :date_year => 0, :since => "", :delay_min => 0, :delay_string => ""})
# Convert to integer
t[:date_minute] = t[:date_minute].to_s.sub(/^0/,"") if (t.has_key?(:date_minute))
t.each do |k,v|
[ :server_port, :vendor_port, :date_month, :date_day,
:date_hour, :date_minute, :queued, :licenses ].each do |symbol|
t[k] = Integer(v) if k == symbol
end
end
t[:date_dayname] = cal[t[:date_dayname].to_s]
t[:date_year] = clock.year
t[:date_year] = t[:date_year] - 1 if (clock.month < t[:date_month])
t[:since] = sprintf( "%2.2d/%2.2d/%2d-%2.2d:%2.2d", t[:date_day], t[:date_month],
t[:date_year], t[:date_hour], t[:date_minute])
t[:delay_min], t[:delay_string] = Tools.delay( clock, t[:date_year],
t[:date_month], t[:date_day], t[:date_hour], t[:date_minute], 0 )
t[:delay_min] = Integer(t[:delay_min] / 60)
t[:delay_string].chop!.chop!.chop!
# Add a key for a borrowed token.
t.merge!( {:borrow => true} ) if ( /linger/ =~ t[:comment] )
end
# Restore the hash.
{ :user => t }
end
end
###
####
module Tools
def Tools.check_file( file )
return false unless file
if File.exist?(file)
File.file?(file)
else
false
end
end
def Tools.delay( clock = Time.now, year, month, day, hour, minute, second )
delay = clock - Time.local(year.to_i,month.to_i,day.to_i,hour.to_i,minute.to_i, second.to_i)
d = delay.divmod(3600.0*24.0)
h = d[1].divmod(3600.0)
m = h[1].divmod(3600.0)[1].divmod(60.0)
s = m[1].divmod(60.0)[1].divmod(60.0)
[ delay, sprintf("%3.3dj%2.2dh%2.2dmin%2.2ds", d[0], h[0], m[0], s[1].round) ]
end
def Tools.grab_list( list_file, separator = ' ' )
return nil unless Tools.check_file(list_file) && File.stat(list_file).readable?
lines = Array.new
list = Array.new
open(list_file).each_line { |l| lines << l.chomp if l }
# 'split' ignore the multiple '/\s/'.
lines.each { |l| list.concat(l.split(separator)) }
# Suppress the spaces if the separator isn't a "\s".
list.each_index { |i| list[i]= list[i].delete(" ") } unless
list.delete_if { |l| l.length < 1 }
list
end
def Tools.create_output( name_of_file, extension = '', mode = "w" )
begin
file_name = name_of_file + extension
line = __LINE__; File.new( file_name, mode )
rescue Errno::EACCES => error_create
STDERR.puts $PROGRAM_NAME + "(#{line})" +
" ERREUR ! create_output(\"#{file_name}\")"
STDERR.puts $PROGRAM_NAME + "(#{line})" +
" ERREUR ! Message = '#{error_create.message}'"
raise error_create
end
end
end
###
您似乎在使用 "newline" 时遇到了问题。
一个好的指导方针是……
* 在规则末尾使用它们(作为终止符)
* 如果它们在语义上不是令牌的一部分,则让父规则使用它们。
假设我有一个文档:
A
B
C
我会将其解析为:
#tokens
rule :a do str("A") end
rule :b do str("B") end
rule :c do str("C") end
rule :nl do str("\n") end
# lines
rule :a_line do a>>nl end
rule :b_line do b>>nl end
rule :c_line do c>>nl end
# doc
rule :doc do a_line>>b_line>>nl>>c_line
请注意,"b_line" 不会同时使用“\n”,因为它应该对其上下文一无所知。
我还注意到您将 "space" 定义为 "str(' ').repeat"。这是 "str(' ').repeat(0)" 的缩写,可以匹配零次。这使得 "space" 是可选的...因此 "space.maybe" 没有意义。
我有一个 Flexlm/Flexnet 许可服务,我想解析该服务的输出。所有输出都是多行的结构化块。我的第一步是解析 lmutil lmstat -c <port@server> -a
的输出以使用许可证和增量。
我尝试使用 Ruby 和 Parslet。所有行都被单独解析。我有一个规则来解析特定类型行的重复,但我无法解析结构化的行块。
我正在寻找定义在多行结构化块中放置 'newline' 语句的位置的法则(在这种情况下最好的词是 'rule')。
我使用 Debian Jessie (stable/x86_64) 和 Ruby 2.1.5p273 和 Parslet 1.6.1-1。
我联系了作者,他很抱歉,但他没有足够的时间来帮助我。看到的网页是:
- URL: www.viget.com/articles/write-you-a-parser-for-fun-and-win
- URL: jmettraux.wordpress.com/2011/05/11/parslet-and-json/
- 递归下降解析器 (Calle) - 视频
- Wicked Good Ruby 2013 - Writing DSL's with Parslet by Jason Garber - Video
- 包含块的多行方法链接的正确 Ruby 样式 - Whosebug
- Ruby parslet:解析多行 - Whosebug
- 如何使用 Parslet 在 Ruby 中处理 C 风格的注释? - Whosebug
我花了很多时间试图理解如何构建多行结构化块的规则。下面是我的源代码,其中包含所有测试字符串和输出。
我的方法是构建:
- 解析行片段的基本规则
- 解析没有'newline'语句的完整行的规则;
- 一个规则来解析重复的相同类型的信息,比如使用的标记行;
- 解析一组数据的规则:表头+重复行;
- 解析重复组的规则。
我不确定第 3 点,我完全迷失了“4”和“5”。
在此先感谢您的帮助。 [ 2017 年 7 月 14 日:部分代码已删除]
#!/usr/bin/env ruby
# This code try to parse the output of 'lmutil lmstat -c <port@server> -a'.
require 'parslet'
require 'parslet/convenience'
require 'pp'
### Begin of the class Lmstat
class Lmstat < Parslet::Parser
###
# Small parts to parse
rule(:digit) { match(/\d/).repeat(1) }
rule(:space) { str(' ').repeat }
rule(:eof) { any.absent? }
rule(:blank_line) { space.maybe >> newline >> space.maybe }
rule(:newline) { str("\r").maybe >> str("\n") }
rule(:txt) { match(/[\w\d\s,_.'",-:]/).repeat }
def parenthese( atom, qte='()' )
if (qte == '()' )
str('(') >> atom >> str(')')
else
str(qte) >> atom >> str(qte)
end
end
###
###
# The header is not parsed for the moment, while I can't
rule (:header) do
# Not define until the other parts are OK.
end
rule(:feature_line) do
feature_usage.as(:feature_line) >> # newline >>
feature_line_id.as(:feature_line_id).repeat.as(:f_line)
end
rule(:feature_line_id) do
feature_version >> newline >> feature_type >> newline >>
feature_user_group >> newline
end
rule(:feature_line_id_group) do
(newline >> feature_line_id).repeat(1).as(:f_line_group) >> newline
end
rule(:feature_usage) do
str("Users of ") >> feature.as(:feature_usage) >> str(':') >> space >>
parenthese( feature_used ) >> space.maybe
end
rule(:feature) { match(/[\w_-]/).repeat }
# Total of 1 license issued; Total of 0 licenses in use
rule(:feature_used) do
feature_token.as(:feature_token_issued) >>
feature_token.as(:feature_token_used) >> space.maybe >> newline.maybe
end
# (Total of 1 license issued; Total of 0 licenses in use)
rule(:feature_token) do
space.maybe >> str('Total of ') >> digit.repeat.as(:feature_token_value) >>
space >> license >> issued_used >>
str(';').maybe >> space.maybe
end
rule(:license) { str('license') >> str('s').maybe >> space }
rule(:issued_used) do
str('issued') | str('in use')
end
# v2015.1231
rule(:version) { match(/[\w\d.-]/).repeat }
# "incr-1"
rule(:vendor) { match(/[\w-]/).repeat }
# "incr-1" v2015.1231, vendor: ansoftd
rule(:feature_version) do
# newline >>
space.maybe >> parenthese( feature.as(:feature), '"' ) >>
space >> version.as(:version) >> str(', vendor: ') >>
vendor.as(:vendor) >> space.maybe >>
str(', expiry: ').maybe >> match(/[\w\d-]/).repeat.as(:expiration).maybe
end
# floating license
# nodelocked license, locked to "ID=12345"
rule(:feature_type) do
space.maybe >>
( (space.maybe >> str("floating license").as(:floating) >> space.maybe) |
(space.maybe >> str('nodelocked license, locked to "ID=') >>
digit.as(:license_id) >> str('"') >> space.maybe)).as(:feature_type) >>
space.maybe
end
# \t 28 RESERVATIONs for GROUP Better_Group (server/27000)
rule(:reserve) do
space.maybe >> str("\t").maybe >> digit.as(:reserve_value) >>
str(" RESERVATION") >> str("s").maybe >> str(" for ") >>
word.as(:reserve_type) >> space >> word.as(:reserve_who) >>
space >>
parenthese( host.as(:server) >> str("/") >> digit.as(:port) )
end
rule(:reserve_group) do
(newline >> reserve).repeat(1).as(:reservation)
end
rule(:feature_user) do
space.maybe >>
word.as(:login) >> space >> host.as(:host_user) >> space >> host.as(:id) >>
space >> parenthese( version.as(:version) ) >> space >> port >> date_queue
end
rule(:feature_user_group) do
(newline >> feature_user).repeat(1).as(:feature_user_group)
end
# queued for 1 license
rule(:queue) do
str('queued for ') >> digit.as(:queued) >> str(' license') >> str('s').maybe
end
rule(:date_queue) do
( ( str(',') >> space >> date >> cmt.as(:comment)) | (space >> queue) )
end
rule(:cmt) do
space.maybe >> match(/[^\r\n]/).repeat#.as(:cmt)
end
rule(:word) { match(/[\w\d-]/).repeat }
rule(:host) { match(/[\w\d_.-]/).repeat }
rule(:port) do
parenthese( host.as(:server) >> str('/') >> digit.as(:server_port) >>
space >> digit.as(:vendor_port) )
end
rule(:date) do
str('start ') >> word.as(:date_dayname) >> space >>
digit.as(:date_month) >> str('/') >> digit.as(:date_day) >> space >>
digit.as(:date_hour) >> str(':') >> digit.as(:date_minute)
end
end
### End of the class Lmstat
###
# Some multiline tests case.
t_feature_line_id = %q{ "incr-2" v9999.9999, vendor: vendor-daemon
floating license
henry abc057 abc057 (v2015.0623) (shoe/28512 3886) queued for 1 license
simon abc057 abc057 (v2014.1110) (shoe/28512 4166), start Fri 11/20 15:37, 10 licenses
simon abc057 abc057 (v2014.1110) (shoe/28512 4166), start Fri 11/20 15:37 queued for 1 license
}
t_feature_line_id_group = %q{ "incr-2" v9999.9999, vendor: vendor-daemon
floating license
henry abc057 abc057 (v2015.0623) (shoe/28512 3886) queued for 1 license
jason abc057 abc057 (v2015.0623) (shoe/28512 3886), start Fri 11/20 14:41
simon abc057 abc057 (v2014.1110) (shoe/28512 4166), start Fri 11/20 15:37, 10 licenses
simon abc057 abc057 (v2014.1110) (shoe/28512 4166), start Fri 11/20 15:37 queued for 1 license
"inc2" v9999.9999, vendor: inc2vendor
floating license
simon abc057 abc057 (v2014.1110) (shoe/28512 4166), start Fri 11/20 15:37, 10 licenses
simon abc057 abc057 (v2014.1110) (shoe/28512 4166), start Fri 11/20 15:37 queued for 1 license }
t_feature_line = %q{Users of ansys: (Total of 9 licenses issued; Total of 6 licenses in use)
"incr-2" v9999.9999, vendor: vendor-daemon
floating license
jason abc057 abc057 (v2015.0623) (shoe/28512 3886), start Fri 11/20 14:41
simon abc057 abc057 (v2014.1110) (shoe/28512 4166), start Fri 11/20 15:37, 10 licenses
simon abc057 abc057 (v2014.1110) (shoe/28512 4166), start Fri 11/20 15:3}
t_feature_line_group = %q{
"incr-2" v9999.9999, vendor: vendor-daemon
floating license
jason abc057 abc057 (v2015.0623) (shoe/28512 3886), start Fri 11/20 14:41
simon abc057 abc057 (v2014.1110) (shoe/28512 4166), start Fri 11/20 15:37, 10 licenses
simon abc057 abc057 (v2014.1110) (shoe/28512 4166), start Fri 11/20 15:37 queued for 1 license
"incr-2" v9999.9999, vendor: vendor-daemon
floating license
jason abc057 abc057 (v2015.0623) (shoe/28512 3886), start Fri 11/20 14:41
simon abc057 abc057 (v2014.1110) (shoe/28512 4166), start Fri 11/20 15:37, 10 licenses
simon abc057 abc057 (v2014.1110) (shoe/28512 4166), start Fri 11/20 15:37}
t_feature_user= %q{jason abc057 abc057 (v2015.0623) (shoe/28512 3886), start Fri 11/20 14:41}
t_feature_group = %q{ "incr-2" v9999.9999, vendor: vendor-daemon
floating license
jason abc057 abc057 (v2015.0623) (shoe/28512 3886), start Fri 11/20 14:41
simon abc057 abc057 (v2014.1110) (shoe/28512 4166), start Fri 11/20 15:37, 10 licenses
jessica abc057 abc057 (v2014.1110) (shoe/28512 4166), start Fri 11/20 15:37
"MATLAB" v35, vendor: MLM, expiry: 01-jan-0000
nodelocked license, locked to "ID=12345"
albert node7563 node7563 (v34) (shoe/27000 201), start Mon 5/23 6:16 (linger: 1235700)
victoria abc087 /dev/pts/1 (v29) (shoe/27000 3401), start Mon 5/23 6:30}
###
###
# Method to test the parsing.
def parse_method(method,str)
lmstat = Lmstat.new
unless lmstat.respond_to?(method)
raise ArgumentError,
"\n\n\t***** ERROR: Unknown method -> '#{method}' ******\n\n",
caller[1..-1]
end
begin
m = "lmstat.#{method}.parse('"+ str + '\')'
puts "=> Test of #{m}"
eval (m)
rescue Parslet::ParseFailed => failure
puts failure.cause.ascii_tree
end
end
###
###
# Not called if 'irb' is used to load the program.
if __FILE__ == $PROGRAM_NAME
puts "\n ###### Multilines #####"
parse_method('feature_user_group',t_feature_user_group)
parse_method("feature_line_id",t_feature_line_id)
pp parse_method("feature_line_id_group",t_feature_line_id_group)
end
输出[ 07/13/2017 : 删除以放置功能版本]
[更新 - 2017 年 4 月 29 日 - 问题已解决] 感谢 Nigel Thorne 的回答,它解决了我的问题。我已根据您的建议更正了 'space' 的规则。
[ 2014 年 7 月 13 日:删除一些文本以放置功能齐全的版本。 ]
[更新 - 2017 年 7 月 13 日 - 申请测试解析]
我已经完成了一个应用程序,用于测试使用 Ruby 和 Parslet 解析 lmstat 的输出。由于解析依赖于每个编辑器,有些情况可能无法涵盖,但使用了 30 多个许可服务来验证解析。
我可以给3个文件:
- parse_lmstat.rb : 使用 './parse_lmstat --help' 获得帮助。测试解析的应用程序。
- readstdin_lmstat.rb :从 STDIN 读取以 YAML 格式生成的经过解析的 lmstat 输出。
- display_lmstat.rb :显示如何访问数据,它用于改进解析的结构。该脚本比
irb
会话更好。它从 STDIN 读取具有 YAML 格式的已解析 lmstat 输出。
一个例子:
~/bin/lmutil lmstat -a -c 1234@licserver | ./parse_lmstat.rb --screen | ./display_lmstat.rb
一个已知错误:当 [CTRL-C] 完成时,信号似乎没有被很好地捕获,Ruby 在某些情况下会发送一些错误消息。
现在,我梦想有一个小型 WEB 应用程序(SINATRA?)到 select 许可证服务器并显示数据,但我不会说 HTML 或 CSS ...任何帮助将不胜感激;-)
您将在下面找到 只有 类 来解析和转换 lmstat 的输出,因为限制为 30000 个字符。
#!/usr/bin/env ruby
#
# class_lmstat.rb
#
# This code try to parse the output of 'lmutil lmstat -c <port@server> -a'.
#
# Scapin - 11/07/2017
#
# For the Whosebug forums
#
require 'parslet'
require 'parslet/convenience'
require 'open3'
### Begin of the class Lmstat
class Lmstat < Parslet::Parser
###
# Small parts to parse
rule(:digit) { match(/\d/).repeat(1) }
rule(:space) { str(' ').repeat(1) }
rule(:eof) { any.absent? }
rule(:blank_line) { space.maybe >> newline >> space.maybe }
rule(:newline) { str("\r").maybe >> str("\n") }
rule(:txt) { match(/[\w_.\)\('\t ",-:\]/).repeat }
rule(:word) { match(/[\w-]/).repeat }
rule(:host) { match(/[\w_\.-]/).repeat }
rule(:cnx_id) { match(/[\/\w_.:]/).repeat }
rule(:cmt) { space.maybe >> match(/[^\r\n]/).repeat }
rule(:error_code) { match(/[,\d-]/).repeat }
def parenthese( atom, qte='()' )
if (qte == '()' )
str('(') >> atom >> str(')')
else
str(qte) >> atom >> str(qte)
end
end
###
root(:lmstat)
rule(:lmstat) do
(header.as(:header) >> body.repeat.as(:service) >> newline).as(:lmstat)
end
###
# The header is not parsed for the moment, while I can't
# handle the multiline block correctly.
#
# lmutil - Copyright (c) 1989-2013 Flexera Software LLC. All Rights Reserved.
# Flexible License Manager status on Fri 11/20/2015 16:39
#
# License server status: 1141@lic-server
# License file(s) on lic-server: /opt/license/soft/vendor1.lic:/opt/license/soft/vendor2.lic:
#
# lic-server: license server UP (MASTER) v11.13
#
# Vendor daemon status (on lic-server):
#
# vendor-daemon: UP v11.13
# Feature usage info:
#
###
rule (:header) do
copyright >> status_date >> newline >>
server >> license_file >> newline >>
server_status >> newline >>
vendor_daemon_status >> newline
end
rule (:body) do
(vendor_daemon.as(:vendor_daemon) >> feature_info.maybe >> newline >>
feature_line.repeat.maybe.as(:features))
end
# lmutil - Copyright (c) 1989-2013 Flexera Software LLC. All Rights Reserved.
rule (:copyright) do
space.maybe >> (str("lmutil - Copyright ") >> match(/./).repeat).as(:copyright) >> newline
end
# Flexible License Manager status on Fri 11/20/2015 16:39
rule(:status_date) do
space.maybe >> str("Flexible License Manager status on ") >>
word.as(:status_dayname) >> space >>
digit.as(:status_month) >> str("/") >> digit.as(:status_day) >>
str("/") >> digit.as(:status_year) >>
str(" ") >> digit.as(:status_hour) >>
str(":") >> digit.as(:status_min) >> newline
end
rule(:server) do
str("License server status: ") >>
digit.as(:server_port1) >> str("@") >> host.as(:server1) >>
( str(",") >> digit.as(:server_port2) >> str("@") >> host.as(:server2) >>
str(",") >> digit.as(:server_port3) >> str("@") >> host.as(:server3) ).maybe >>
newline
end
# License file(s) on lic-server: /opt/license/soft/licfile-1.lic:/opt/soft/licfile-2.lic:
rule(:license_file) do
space.maybe >> str("License file(s) on ") >>
match(/[\w\d._-]/).repeat.as(:license_files_server) >>
str(": ") >> txt.as(:license_files_names) >> newline
end
rule(:server_status) do
(space.maybe >> host.as(:server_host) >> str(": ") >>
( server_up | server_down) >> newline).repeat(0).as(:server_list)
end
rule(:server_up) do
str("license server ")>>str("UP").as(:server_up)>>
server_pos.maybe >> str(" ") >> cmt.as(:server_version)
end
rule(:server_pos) do
space >> parenthese( match(/[A-Za-z]/).repeat.as(:server_role))
end
# licserver: Cannot connect to license server system. (-15,570:115 "Operation now in progress")
rule(:server_down) do
space.maybe >> str("Cannot connect to license server system").as(:server_down) >>
str(". ") >> cmt.as(:server_error)
end
rule(:vendor_daemon_status) do
str("Vendor daemon status (on ") >> host.as(:server_daemon) >>
str("):") >> space.maybe >> newline
end
rule(:vendor_daemon) do
( vendor_daemon_up | vendor_daemon_down )
end
rule(:vendor_daemon_up) do
space.maybe >> word.as(:daemon) >> str(": ") >> word.as(:daemon_status) >>
space >> host.as(:daemon_version) >> newline
end
rule(:vendor_daemon_down_ini) do
space.maybe >> word.as(:daemon) >> str(": The desired vendor daemon is down. ") >>
parenthese( error_code.as(:daemon_status) ) >> space.maybe >> newline
end
# \n\n dconcept: No socket connection to license server manager. (-7,96)
rule(:vendor_daemon_down) do
space.maybe >> word.as(:daemon) >> str(": The desired vendor daemon is down. ") >>
parenthese( error_code.as(:daemon_status) ) >> space.maybe >> newline
space.maybe >> word.as(:vendor_daemon_down_msg_feature).maybe >>
str(': No socket connection to license server manager.').maybe >> space.maybe >>
cmt.as(:vendor_daemon_down_msg).maybe >> newline.maybe
end
rule(:feature_info) do
space.maybe >> str("Feature usage info:") >> space.maybe >> newline
end
###
# Users of soft_a: (Total of 1 license issued; Total of 0 licenses in use)
#
# "incr-1" v2015.1231, vendor: soft_ad
# floating license
#
# 28 RESERVATIONs for GROUP Better_Group (server/27000)
# 1 RESERVATION for USER toni (server/27000)
# scott abc056 abc056 (v2015.0623) (shoe/28512 3644), start Fri 11/20 15:45, 2 licenses
# scott abc056 abc056 (v2015.0623) (shoe/28512 4669), start Fri 11/20 15:45, 10 licenses
rule(:feature_line) do
feature_usage.as(:feature_line) >> newline >>
feature_line_id.repeat(0).as(:feature_line_id)
end
# "incr-1" v2015.1231, vendor: soft_ad
# floating license
#
# scott abc056 abc056 (v2015.0623) (shoe/28512 3644), start Fri 11/20 15:45, 2 licenses
# scott abc056 abc056 (v2015.0623) (shoe/28512 4669), start Fri 11/20 15:45, 10 licenses
rule(:feature_line_id) do
feature_version >> newline >> feature_type >> newline >>
# ( reserve.as(:reservation) | feature_user.as(:user)).repeat(1).as(:users) >> newline
( reserve.as(:reservation) | feature_user.as(:user)).repeat(1).as(:who) >> newline
end
# Users of soft_a: (Total of 1 license issued; Total of 0 licenses in use)
# Users of SOFT_B: (Uncounted, node-locked)
# Users of soft_c: (Error: 6 licenses, unsupported by licensed server)
rule(:feature_usage) do
str("Users of ") >> feature.as(:feature_name) >> str(':') >> space >>
parenthese( feature_used ) >> space.maybe >> newline
end
# Total of 1 license issued; Total of 0 licenses in use
# Uncounted, node-locked
rule(:feature_used) do
( ( feature_token.as(:feature_token_issued) >> feature_token.as(:feature_token_used)) |
( word.as(:feature_token_issued) >> str(', ') >> word.as(:feature_token_used) ) |
( str('Error: ') >> digit.repeat.as(:feature_token_error) >> space >> str( 'license') >>
str('s').maybe >> str(', ') >>
match(/[\w_', :-]/).repeat.as(:feature_token_error_cause) ) ) >>
space.maybe >> newline.maybe
end
# (Total of 1 license issued; Total of 0 licenses in use)
rule(:feature_token) do
space.maybe >> str('Total of ') >> digit.repeat.as(:feature_token_value) >>
space >> license >> issued_used >>
str(';').maybe >> space.maybe
end
rule(:license) { str('license') >> str('s').maybe >> space }
rule(:issued_used) do
str('issued') | str('in use')
end
# v2015.1231
rule(:version) { match(/[\w\d.-]/).repeat }
# "incr-1"
rule(:vendor) { match(/[\w-]/).repeat }
rule(:feature) { match(/[\w\d\/_+-]/).repeat }
# "incr-1" v2015.1231, vendor: soft_ad
rule(:feature_version) do
# newline >>
space >> parenthese( feature.as(:feature), '"' ) >>
space >> version.as(:version) >> str(', vendor: ') >>
vendor.as(:vendor) >> space.maybe >>
str(', expiry: ').maybe >> match(/[\w\d-]/).repeat.as(:expiration).maybe
end
rule(:feature_type) do
space >> ( float_type | node_type ).as(:feature_type)
end
# floating license
rule(:float_type) do
str("floating license").as(:floating) >> cmt.maybe >> newline
end
# nodelocked license, locked to "ID=654321"
# nodelocked license locked to NOTHING (hostid=ANY)
# uncounted nodelocked license locked to NOTHING (hostid=ANY)
# uncounted nodelocked license, locked to Vendor-defined "PTC_HOSTID=01-0A-01-0A-01"
rule(:node_type) do
str('uncounted ').maybe >> str("nodelocked license") >> str(',').maybe >> str(' locked to ').maybe >>
( ( str('"ID=') >> digit.as(:nodelocked_id) >> str('"') ) |
( host.as(:nodelocked_to) >> space >> parenthese(str('hostid=') >> host.as(:nodelocked_hostid)) ) |
( host.as(:nodelocked_to) >> space >>
parenthese(match(/[\w:_=' -]/).repeat.as(:nodelocked_hostid), '"') ) ) >>
space.maybe >> newline
end
# \t 28 RESERVATIONs for GROUP Better_Group (server/27000)
rule(:reserve) do
space.maybe >> str("\t").maybe >> digit.as(:reserve_value) >>
str(" RESERVATION") >> str("s").maybe >> str(" for ") >>
word.as(:reserve_type) >> space >> word.as(:reserve_who) >>
space >>
parenthese( host.as(:server) >> str("/") >> digit.as(:port) ) >>
newline
end
rule(:feature_user) do
(u_std | u_aselta | u_ans | u_c1 | u_c2 )
end
# scott abc056 abc056 (v2015.0623) (shoe/28512 3644), start Fri 11/20 15:45, 2 licenses
# albert node7563 node7563 (v34) (shoe/27000 201), start Mon 5/23 6:16 (linger: 1235700)
# hector node088 dev/tty (v2015.0312) (licserver/1446 3730), start Thu 11/19 9:08
# will pim.my.domain.org pim.my.domain.org 6656 (v2016.1129) (licserver/1446 2216), start Fri 5/12 14:51
rule(:u_std) do
space >> word.as(:login) >> space >> host.as(:host_user) >> space >>
cnx_id.as(:host_id) >>
space >> parenthese( version.as(:version) ) >> space >> port >> date_queue >>
newline
end
# scott cat :0 Token Lic (v7.000) (shoe/5300 15434), start Thu 7/6 17:07
rule(:u_c1) do
space >> word.as(:login) >> space >> host.as(:host_user) >> space >>
cnx_id.as(:host_id) >> space >> match(/[^(]/).repeat.as(:common_name) >>
parenthese( version.as(:version) ) >> space >> port >> date_queue >>
newline
end
# jessie cat bird:1144.0 APS Multi-core (Max. 16 cores) (v11.100) (licserver/5303 13188), start Thu 7/6 15:22, 4 licenses
rule(:u_c2) do
space >> word.as(:login) >> space >> host.as(:host_user) >> space >>
cnx_id.as(:host_id) >> space >> match(/[^(]/).repeat.as(:common_name) >>
str('(') >> match(/[^)]/).repeat.as(:common_info) >> str(')') >> space >>
parenthese( version.as(:version) ) >> space >> port >> date_queue >>
newline
end
# tiger pam.my.domain.org pam.my.domain.org 6656 (v2016.1129) (licserver/1446 2216), start Fri 5/12 14:51
rule(:u_ans) do
space >> word.as(:login) >> space >> host.as(:host_user) >> space >>
cnx_id.as(:host_id) >> ( space >> host.as(:further) ).maybe >>
space >> parenthese( version.as(:version) ) >> space >> port >> date_queue >>
newline
end
# clark node07 SOMETHING Inscale / grid (worker) (v1.0) (licserv01/27016 5506), start Fri 4/28 13:42, 4 licenses
# bunny orca SOMETHING Inscale / graphical (v1.0) (licserv01/27016 650), start Thu 4/13 10:27
rule(:u_aselta) do
space >> word.as(:login) >> space >> host.as(:host_user) >> space >>
word.as(:daemon) >> space >> word.as(:soft) >> str(" / ") >>
word.as(:function) >> (space >> parenthese( word.as(:tools) )).maybe >>
space >> parenthese( version.as(:version) ) >> space >> port >> date_queue >>
newline
end
# queued for 1 license
rule(:queue) do
str('queued for ') >> digit.as(:queued) >> str(' license') >> str('s').maybe
end
rule(:lic) do
str(',') >> space >> digit.as(:licenses) >> str(' license') >> str('s').maybe
end
rule(:date_queue) do
( ( str(',') >> space >> date >> ( lic | cmt.as(:comment))) | (space >> queue) )
end
rule(:port) do
parenthese( host.as(:server) >> str('/') >> digit.as(:server_port) >>
space >> digit.as(:vendor_port) )
end
rule(:date) do
str('start ') >> word.as(:date_dayname) >> space >>
digit.as(:date_month) >> str('/') >> digit.as(:date_day) >> space >>
digit.as(:date_hour) >> str(':') >> digit.as(:date_minute)
end
end
### End of the class Lmstat
### Begin of the class Trans
class Trans < Parslet::Transform
rule(:feature_token_value => simple(:v)) { Integer(v) }
rule(:user => subtree(:t)) do
if ( t.has_key?(:date_month) )
cal = { "Sun"=>"Dimanche", "Mon"=>"Lundi", "Tue"=>"Mardi",
"Wed"=>"Mercredi", "Thu"=>"Jeudi", "Fri"=>"Vendredi"}
clock = Time.now
# Addition of keys
t.merge!( { :date_year => 0, :since => "", :delay_min => 0, :delay_string => ""})
# Convert to integer
t[:date_minute] = t[:date_minute].to_s.sub(/^0/,"") if (t.has_key?(:date_minute))
t.each do |k,v|
[ :server_port, :vendor_port, :date_month, :date_day,
:date_hour, :date_minute, :queued, :licenses ].each do |symbol|
t[k] = Integer(v) if k == symbol
end
end
t[:date_dayname] = cal[t[:date_dayname].to_s]
t[:date_year] = clock.year
t[:date_year] = t[:date_year] - 1 if (clock.month < t[:date_month])
t[:since] = sprintf( "%2.2d/%2.2d/%2d-%2.2d:%2.2d", t[:date_day], t[:date_month],
t[:date_year], t[:date_hour], t[:date_minute])
t[:delay_min], t[:delay_string] = Tools.delay( clock, t[:date_year],
t[:date_month], t[:date_day], t[:date_hour], t[:date_minute], 0 )
t[:delay_min] = Integer(t[:delay_min] / 60)
t[:delay_string].chop!.chop!.chop!
# Add a key for a borrowed token.
t.merge!( {:borrow => true} ) if ( /linger/ =~ t[:comment] )
end
# Restore the hash.
{ :user => t }
end
end
###
####
module Tools
def Tools.check_file( file )
return false unless file
if File.exist?(file)
File.file?(file)
else
false
end
end
def Tools.delay( clock = Time.now, year, month, day, hour, minute, second )
delay = clock - Time.local(year.to_i,month.to_i,day.to_i,hour.to_i,minute.to_i, second.to_i)
d = delay.divmod(3600.0*24.0)
h = d[1].divmod(3600.0)
m = h[1].divmod(3600.0)[1].divmod(60.0)
s = m[1].divmod(60.0)[1].divmod(60.0)
[ delay, sprintf("%3.3dj%2.2dh%2.2dmin%2.2ds", d[0], h[0], m[0], s[1].round) ]
end
def Tools.grab_list( list_file, separator = ' ' )
return nil unless Tools.check_file(list_file) && File.stat(list_file).readable?
lines = Array.new
list = Array.new
open(list_file).each_line { |l| lines << l.chomp if l }
# 'split' ignore the multiple '/\s/'.
lines.each { |l| list.concat(l.split(separator)) }
# Suppress the spaces if the separator isn't a "\s".
list.each_index { |i| list[i]= list[i].delete(" ") } unless
list.delete_if { |l| l.length < 1 }
list
end
def Tools.create_output( name_of_file, extension = '', mode = "w" )
begin
file_name = name_of_file + extension
line = __LINE__; File.new( file_name, mode )
rescue Errno::EACCES => error_create
STDERR.puts $PROGRAM_NAME + "(#{line})" +
" ERREUR ! create_output(\"#{file_name}\")"
STDERR.puts $PROGRAM_NAME + "(#{line})" +
" ERREUR ! Message = '#{error_create.message}'"
raise error_create
end
end
end
###
您似乎在使用 "newline" 时遇到了问题。
一个好的指导方针是…… * 在规则末尾使用它们(作为终止符) * 如果它们在语义上不是令牌的一部分,则让父规则使用它们。
假设我有一个文档:
A
B
C
我会将其解析为:
#tokens
rule :a do str("A") end
rule :b do str("B") end
rule :c do str("C") end
rule :nl do str("\n") end
# lines
rule :a_line do a>>nl end
rule :b_line do b>>nl end
rule :c_line do c>>nl end
# doc
rule :doc do a_line>>b_line>>nl>>c_line
请注意,"b_line" 不会同时使用“\n”,因为它应该对其上下文一无所知。
我还注意到您将 "space" 定义为 "str(' ').repeat"。这是 "str(' ').repeat(0)" 的缩写,可以匹配零次。这使得 "space" 是可选的...因此 "space.maybe" 没有意义。