rabbitmq parser: tree transformation to ruby objects

This commit is contained in:
Dominik Richter 2017-04-04 10:39:05 +02:00
parent ea1137b0ee
commit 860750d967

View file

@ -5,21 +5,21 @@ class ErlAx < Parslet::Parser
# only designed for rabbitmq config files for now:
rule(:outermost) { filler? >> array.maybe >> dot.maybe }
rule(:expression) {
rule(:exp) {
(tuple | array | binary | string | bool | identifier | float | integer) >> filler?
}
rule(:array) {
str('[') >> filler? >> (
expression >>
(comma >> expression).repeat
exp.repeat(1) >>
(comma >> exp).repeat
).maybe.as(:array) >> str(']') >> filler?
}
rule(:tuple) {
str('{') >> filler? >> (
expression >> filler? >>
(comma >> expression).repeat
exp.repeat(1) >> filler? >>
(comma >> exp).repeat
).maybe.as(:tuple) >> str('}') >> filler?
}
@ -72,6 +72,7 @@ end
require 'minitest/autorun'
require 'minitest/spec'
describe ErlAx do
@ -96,35 +97,35 @@ describe ErlAx do
end
it 'parse simple root array' do
_(parsestr("[].\n")).must_equal "{:array=>nil}"
_(parsestr("[].\n")).must_equal '{:array=>nil}'
end
it 'parses a root array with an int' do
_(parsestr('[1].')).must_equal '{:array=>{:integer=>"1"@1}}'
_(parsestr('[1].')).must_equal '{:array=>[{:integer=>"1"@1}]}'
end
it 'parses a root array with a float' do
_(parsestr('[1.1].')).must_equal '{:array=>{:float=>{:integer=>"1"@1, :e=>".1"@2}}}'
_(parsestr('[1.1].')).must_equal '{:array=>[{:float=>{:integer=>"1"@1, :e=>".1"@2}}]}'
end
it 'parses a root array with a double quoted string' do
_(parsestr('["st\"r"].')).must_equal '{:array=>{:string=>"st\\\\\\"r"@2}}'
_(parsestr('["st\"r"].')).must_equal '{:array=>[{:string=>"st\\\\\\"r"@2}]}'
end
it 'parses a root array with a single quoted string' do
_(parsestr('[\'st\\\'r\'].')).must_equal '{:array=>{:string=>"st\\\\\'r"@2}}'
_(parsestr('[\'st\\\'r\'].')).must_equal '{:array=>[{:string=>"st\\\\\'r"@2}]}'
end
it 'parses a root array with a binary' do
_(parsestr('[<<0, G, B>>].')).must_equal '{:array=>{:binary=>"0, G, B"@3}}'
_(parsestr('[<<0, G, B>>].')).must_equal '{:array=>[{:binary=>"0, G, B"@3}]}'
end
it 'parses a root array with a boolean' do
_(parsestr('[true].')).must_equal '{:array=>{:bool=>"true"@1}}'
_(parsestr('[true].')).must_equal '{:array=>[{:bool=>"true"@1}]}'
end
it 'parses a root array with an identifier' do
_(parsestr('[hello_world123].')).must_equal '{:array=>{:identifier=>"hello_world123"@1}}'
_(parsestr('[hello_world123].')).must_equal '{:array=>[{:identifier=>"hello_world123"@1}]}'
end
it 'parses a root array with multiple elements' do
@ -132,23 +133,23 @@ describe ErlAx do
end
it 'parses a root array with an tuple' do
_(parsestr('[{}].')).must_equal '{:array=>{:tuple=>nil}}'
_(parsestr('[{}].')).must_equal '{:array=>[{:tuple=>nil}]}'
end
it 'parses a root array with an tuple' do
_(parsestr('[{handshake_timeout, 100}].')).must_equal '{:array=>{:tuple=>[{:identifier=>"handshake_timeout"@2}, {:integer=>"100"@21}]}}'
_(parsestr('[{handshake_timeout, 100}].')).must_equal '{:array=>[{:tuple=>[{:identifier=>"handshake_timeout"@2}, {:integer=>"100"@21}]}]}'
end
it 'parses a root array with a stringy tuple' do
_(parsestr('[{ab, [{"c", 1},{"d",2}]}].')).must_equal '{:array=>{:tuple=>[{:identifier=>"ab"@2}, {:array=>[{:tuple=>[{:string=>"c"@9}, {:integer=>"1"@13}]}, {:tuple=>[{:string=>"d"@18}, {:integer=>"2"@21}]}]}]}}'
_(parsestr('[{ab, [{"c", 1},{"d",2}]}].')).must_equal '{:array=>[{:tuple=>[{:identifier=>"ab"@2}, {:array=>[{:tuple=>[{:string=>"c"@9}, {:integer=>"1"@13}]}, {:tuple=>[{:string=>"d"@18}, {:integer=>"2"@21}]}]}]}]}'
end
it 'parses a complex deep array-tuple mix' do
_(parsestr('[{rabbit,[{two,[]}]}].')).must_equal '{:array=>{:tuple=>[{:identifier=>"rabbit"@2}, {:array=>{:tuple=>[{:identifier=>"two"@11}, {:array=>nil}]}}]}}'
_(parsestr('[{rabbit,[{two,[]}]}].')).must_equal '{:array=>[{:tuple=>[{:identifier=>"rabbit"@2}, {:array=>[{:tuple=>[{:identifier=>"two"@11}, {:array=>nil}]}]}]}]}'
end
it 'parses a complex multi array tuple mix' do
_(parsestr('[{rabbit,[{two,[]}]}, 3, false].')).must_equal '{:array=>[{:tuple=>[{:identifier=>"rabbit"@2}, {:array=>{:tuple=>[{:identifier=>"two"@11}, {:array=>nil}]}}]}, {:integer=>"3"@22}, {:bool=>"false"@25}]}'
_(parsestr('[{rabbit,[{two,[]}]}, 3, false].')).must_equal '{:array=>[{:tuple=>[{:identifier=>"rabbit"@2}, {:array=>[{:tuple=>[{:identifier=>"two"@11}, {:array=>nil}]}]}]}, {:integer=>"3"@22}, {:bool=>"false"@25}]}'
end
it 'parses a complex array-tuple mix with comments' do
@ -161,10 +162,81 @@ describe ErlAx do
{tcp_listeners, [5672]}
]}].
'
_(parsestr(s)).must_equal '{:array=>{:tuple=>[{:identifier=>"rabbit"@29}, {:array=>{:tuple=>[{:identifier=>"tcp_listeners"@75}, {:array=>{:integer=>"5672"@91}}]}}]}}'
_(parsestr(s)).must_equal '{:array=>[{:tuple=>[{:identifier=>"rabbit"@29}, {:array=>[{:tuple=>[{:identifier=>"tcp_listeners"@75}, {:array=>[{:integer=>"5672"@91}]}]}]}]}]}'
end
end
class ErlEr < Parslet::Transform
class Tuple < Array; end
class Identifier < String; end
rule(string: simple(:x)) { x.to_s }
rule(string: []) { '' }
rule(integer: simple(:x)) { x.to_i }
rule(float: { integer: simple(:a), e: simple(:b) }) { (a+b).to_f }
rule(bool: 'true') { true }
rule(bool: 'false') { false }
rule(identifier: simple(:x)) { Identifier.new(x.to_s) }
# TODO: binary!
rule(array: subtree(:x)) { Array(x) }
rule(tuple: subtree(:x)) {
x.nil? ? Tuple.new : Tuple.new(x)
}
end
describe ErlEr do
def parse(c)
ErlEr.new.apply(ErlAx.new.parse(c))
end
it 'parses a real rabbitmq config file' do
it 'transforms and empty file' do
_(parse('')).must_equal ''
end
it 'transforms an empty array' do
_(parse('[].')).must_equal []
end
it 'transforms a simple array with one string' do
_(parse('["one"].')).must_equal ['one']
end
it 'transforms a simple array with an empty string' do
_(parse('[\'\'].')).must_equal ['']
end
it 'transforms a simple array with multiple values' do
_(parse('[1, 1.1, true, false, "ok"].')).must_equal [1, 1.1, true, false, 'ok']
end
it 'transforms a deep array' do
_(parse('[[[[1]]]].')).must_equal [[[[1]]]]
end
it 'transforms an empty tuple' do
_(parse('[{}].')).must_equal [ErlEr::Tuple.new]
end
it 'transforms a tuple with one element' do
_(parse('[{1}].')).must_equal [ErlEr::Tuple.new([1])]
end
it 'transforms a tuple with multiple elements' do
_(parse('[{id123, 1, 1.1}].')).must_equal [ErlEr::Tuple.new([ErlEr::Identifier.new('id123'), 1, 1.1])]
end
it 'transforms a deep tuple' do
_(parse('[{{{1}}}].')).must_equal [ErlEr::Tuple.new([ErlEr::Tuple.new([ErlEr::Tuple.new([1])])])]
end
it 'transforms a deep mix of tuple and array' do
_(parse('[{[{1}]}].')).must_equal [ErlEr::Tuple.new([[ErlEr::Tuple.new([1])]])]
end
end
describe 'complex use-case' do
it 'parses a tricky rabbitmq config file' do
f = 'rabbitmq.config'
unless File.file?(f)
puts "NO #{f}, skipping this check!"
@ -172,6 +244,8 @@ describe ErlAx do
end
puts 'Wheeee, testing a real rabbitmq nasty config file'
c = File.read(f)
parsestr(c)
_(ErlAx.new.parse(c).to_s).must_be_instance_of String
res = ErlEr.new.apply(ErlAx.new.parse(c))
# require "pry"; binding.pry
end
end