figtree 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b6b5da9f31e285ace11997fa8066b4fb9926ad3a
4
- data.tar.gz: 1c11bdf776b084922e35c782e9737d3275f32ddd
3
+ metadata.gz: 027349cba07d928b55c539c415a8677994c2ad4f
4
+ data.tar.gz: 2f16fbdf432e44763f6d07fee7d56f31a96f1b72
5
5
  SHA512:
6
- metadata.gz: b515887114e72ca380c854619e5596e2027d7f120c372ff1c2e48b03d35a6bcd50cfa792dac15c435ac7a7639844bab810b95a7813bba4bbffc2e722653c80b6
7
- data.tar.gz: 7ccf9fb5da3a512f019c37652ed680f64b502beee938974a560a7b2c25dd5e8bfd1a6a8d414787e3df354a17038d3a705907c61c9709da6b5e64c465372e5c75
6
+ metadata.gz: 70cd81c1f602ddb2b8e845958ad005592c663abb8fe5d7b85a69a4db618fea5408b11312013484ec49cd42595f3495869c30a3bf48483acf8af352eab220e0d1
7
+ data.tar.gz: 3cc856774fb2130c8152e24e25e63b2f5561a9eea951fccffeb2f906c9d752072a3e1e06fc260b43a761c06a3e032810e802eac0389b701eda69119ba6bfb5bf
@@ -0,0 +1,5 @@
1
+ - Version 0.0.1: initial set up
2
+ - Version 0.0.2: some bug fixes, overrides
3
+ - Version 1.0.0: moving away from #load_conifg to just #new
4
+ - Version 1.0.1: refactoring in IniConfig
5
+ - Version 1.2.0: adding support for pre-group comments and ip addresses
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- figtree (1.0.0)
4
+ figtree (1.2.0)
5
5
  parslet (~> 1.7)
6
6
  wannabe_bool (~> 0.2)
7
7
 
@@ -0,0 +1,85 @@
1
+ # from https://github.com/kschiess/parslet/blob/master/example/ip_address.rb
2
+ module Figtree
3
+ # Must be used in concert with IPv4
4
+ module IPv6
5
+ include Parslet
6
+
7
+ rule(:colon) { str(':') }
8
+ rule(:dcolon) { colon >> colon }
9
+
10
+ # h16 :
11
+ def h16r(times)
12
+ (h16 >> colon).repeat(times, times)
13
+ end
14
+
15
+ # : h16
16
+ def h16l(times)
17
+ (colon >> h16).repeat(0,times)
18
+ end
19
+
20
+ # A 128-bit IPv6 address is divided into eight 16-bit pieces. Each piece is
21
+ # represented numerically in case-insensitive hexadecimal, using one to four
22
+ # hexadecimal digits (leading zeroes are permitted). The eight encoded
23
+ # pieces are given most-significant first, separated by colon characters.
24
+ # Optionally, the least-significant two pieces may instead be represented in
25
+ # IPv4 address textual format. A sequence of one or more consecutive
26
+ # zero-valued 16-bit pieces within the address may be elided, omitting all
27
+ # their digits and leaving exactly two consecutive colons in their place to
28
+ # mark the elision.
29
+ rule(:ipv6) {
30
+ (
31
+ (
32
+ h16r(6) |
33
+ dcolon >> h16r(5) |
34
+ h16.maybe >> dcolon >> h16r(4) |
35
+ (h16 >> h16l(1)).maybe >> dcolon >> h16r(3) |
36
+ (h16 >> h16l(2)).maybe >> dcolon >> h16r(2) |
37
+ (h16 >> h16l(3)).maybe >> dcolon >> h16r(1) |
38
+ (h16 >> h16l(4)).maybe >> dcolon
39
+ ) >> ls32 |
40
+ (h16 >> h16l(5)).maybe >> dcolon >> h16 |
41
+ (h16 >> h16l(6)).maybe >> dcolon
42
+ ).as(:ipv6)
43
+ }
44
+
45
+ rule(:h16) {
46
+ hexdigit.repeat(1,4)
47
+ }
48
+
49
+ rule(:ls32) {
50
+ (h16 >> colon >> h16) |
51
+ ipv4
52
+ }
53
+
54
+ rule(:hexdigit) {
55
+ digit | match("[a-fA-F]")
56
+ }
57
+ end
58
+
59
+ module IPv4
60
+ include Parslet
61
+
62
+ # A host identified by an IPv4 literal address is represented in
63
+ # dotted-decimal notation (a sequence of four decimal numbers in the range 0
64
+ # to 255, separated by "."), as described in [RFC1123] by reference to
65
+ # [RFC0952]. Note that other forms of dotted notation may be interpreted on
66
+ # some platforms, as described in Section 7.4, but only the dotted-decimal
67
+ # form of four octets is allowed by this grammar.
68
+ rule(:ipv4) {
69
+ (dec_octet >> str('.') >> dec_octet >> str('.') >>
70
+ dec_octet >> str('.') >> dec_octet).as(:ipv4)
71
+ }
72
+
73
+ rule(:dec_octet) {
74
+ str('25') >> match("[0-5]") |
75
+ str('2') >> match("[0-4]") >> digit |
76
+ str('1') >> digit >> digit |
77
+ match('[1-9]') >> digit |
78
+ digit
79
+ }
80
+
81
+ rule(:digit) {
82
+ match('[0-9]')
83
+ }
84
+ end
85
+ end
@@ -1,21 +1,29 @@
1
1
  require 'parslet'
2
+ require 'figtree/ip_rules'
2
3
 
3
4
  module Figtree
4
5
  # ConFIG into a Tree :)
5
6
  class Parser < Parslet::Parser
7
+ include IPv4
8
+ include IPv6
9
+
6
10
  rule(:eof) { any.absent? }
7
11
  rule(:group_title) { match('[a-zA-Z]').repeat(1) }
8
- rule(:newline) { match("\\n").repeat(1) >> match("\\r").maybe }
9
- rule(:space) { match('\s').repeat(0) }
12
+ rule(:space) { match("\s").repeat(0) }
13
+ rule(:newline) { match("\n") >> match("\r").maybe }
14
+
10
15
  rule(:grouper) do
16
+ newline.maybe >>
11
17
  str('[') >>
12
18
  group_title.as(:group_title) >>
13
19
  str(']')
14
20
  end
15
21
 
16
22
  rule(:comment) do
17
- str(';') >>
18
- (newline.absent? >> any).repeat
23
+ # comments go uncaptured
24
+ (str(';') >>
25
+ (newline.absent? >> any).repeat) >>
26
+ newline.maybe
19
27
  end
20
28
 
21
29
  rule(:string) do
@@ -25,6 +33,7 @@ module Figtree
25
33
  end
26
34
 
27
35
  rule(:boolean) do
36
+ # expand this check
28
37
  (str('no') | str('yes')).as(:boolean)
29
38
  end
30
39
 
@@ -32,6 +41,10 @@ module Figtree
32
41
  match('[0-9]').repeat(1).as(:number)
33
42
  end
34
43
 
44
+ rule(:ip_address) do
45
+ (ipv4 | ipv6).as(:ip_address)
46
+ end
47
+
35
48
  rule(:array) do
36
49
  (match('[a-zA-Z]').repeat(1) >>
37
50
  (str(',') >>
@@ -59,7 +72,16 @@ module Figtree
59
72
  space >>
60
73
  str("=") >>
61
74
  space >>
62
- (number | boolean | array | snake_case_key | file_path | string)
75
+ # this ordering matters
76
+ # we are roughly moving from more
77
+ # to less specific
78
+ (ip_address |
79
+ number |
80
+ boolean |
81
+ array |
82
+ snake_case_key |
83
+ file_path |
84
+ string)
63
85
  end
64
86
 
65
87
  rule(:override_assignment) do
@@ -86,6 +108,12 @@ module Figtree
86
108
  repeat.maybe
87
109
  end
88
110
 
89
- root(:group)
111
+ rule(:comment_or_group) do
112
+ comment.maybe >>
113
+ newline.maybe >>
114
+ group.maybe
115
+ end
116
+
117
+ root(:comment_or_group)
90
118
  end
91
119
  end
@@ -1,66 +1,74 @@
1
1
  require 'parslet'
2
2
  require 'ostruct'
3
+ require 'ipaddr'
3
4
  require 'wannabe_bool'
4
5
 
5
6
  module Figtree
6
- # a transformer takes a parsed, valid AST and applies rules, usually
7
- # in a context free manner
8
- class Transformer < Parslet::Transform
9
- # TODO these could largely be consolidated with some rearrangement
10
- # these are all type conversions
11
- rule(:snake_case_key => simple(:key), :number => simple(:value)) do
12
- {
13
- key.to_sym => Integer(value)
14
- }
15
- end
16
- rule(:snake_case_key => simple(:key), :string => simple(:value)) do
17
- {
18
- key.to_sym => String(value)
19
- }
20
- end
21
- rule(:snake_case_key => simple(:key), :file_path => simple(:value)) do
22
- {
23
- key.to_sym => String(value)
24
- }
25
- end
26
- # depends on wannabe_bool refining String class
27
- rule(:snake_case_key => simple(:key), :boolean => simple(:value)) do
28
- {
29
- key.to_sym => String(value).to_b
30
- }
31
- end
32
- rule(:snake_case_key => simple(:key), :array => simple(:value)) do
33
- {
34
- key.to_sym => String(value).split(",")
35
- }
36
- end
7
+ # a transformer takes a parsed, valid AST and applies rules, usually
8
+ # in a context free manner
9
+ class Transformer < Parslet::Transform
10
+ # TODO these could largely be consolidated with some rearrangement
11
+ # these are all type conversions
12
+ rule(:snake_case_key => simple(:key), :number => simple(:value)) do
13
+ {
14
+ key.to_sym => Integer(value)
15
+ }
16
+ end
17
+ rule(:snake_case_key => simple(:key), :string => simple(:value)) do
18
+ {
19
+ key.to_sym => String(value)
20
+ }
21
+ end
22
+ rule(:snake_case_key => simple(:key), :file_path => simple(:value)) do
23
+ {
24
+ key.to_sym => String(value)
25
+ }
26
+ end
27
+ # depends on wannabe_bool refining String class
28
+ rule(:snake_case_key => simple(:key), :boolean => simple(:value)) do
29
+ {
30
+ key.to_sym => String(value).to_b
31
+ }
32
+ end
33
+ rule(:snake_case_key => simple(:key), :array => simple(:value)) do
34
+ {
35
+ key.to_sym => String(value).split(",")
36
+ }
37
+ end
37
38
 
38
- # ini files are trees of a fixed height, if the file handle is the root
39
- # subgroups are its children, and subgroup members are the next level of children
40
- rule(:group => subtree(:group_members)) do
41
- group_title = group_members[0][:group_title].to_sym
42
- group_values = Subgroup.new(group_members[1..-1].reduce({}, :merge!))
43
- {
44
- group_title => group_values
45
- }
46
- end
39
+ rule(:snake_case_key => simple(:key), :ip_address => subtree(:value)) do
40
+ # right now we're not distinguishing what kind of ip it is
41
+ {
42
+ key.to_sym => IPAddr.new((value.values.first.to_s))
43
+ }
44
+ end
47
45
 
48
- # where does overrides come from? an argument into #apply on
49
- # Transformer, that allows an additional capture outside the AST
50
- # to be added to the context of the transform
51
- rule(
52
- :key_to_be_overridden => subtree(:overridden_key),
53
- :optional_key => subtree(:overriding_key),
54
- :file_path => subtree(:new_file_path),
55
- ) do
56
- if override.to_sym == overriding_key[:snake_case_key].to_sym
57
- {
58
- overridden_key[:snake_case_key] => String(new_file_path)
59
- }
60
- else
61
- {
62
- }
63
- end
64
- end
65
- end
46
+ # ini files are trees of a fixed height, if the file handle is the root
47
+ # subgroups are its children, and subgroup members are the next level of children
48
+ rule(:group => subtree(:group_members)) do
49
+ group_title = group_members[0][:group_title].to_sym
50
+ group_values = Subgroup.new(group_members[1..-1].reduce({}, :merge!))
51
+ {
52
+ group_title => group_values
53
+ }
54
+ end
55
+
56
+ # where does overrides come from? an argument into #apply on
57
+ # Transformer, that allows an additional capture outside the AST
58
+ # to be added to the context of the transform
59
+ rule(
60
+ :key_to_be_overridden => subtree(:overridden_key),
61
+ :optional_key => subtree(:overriding_key),
62
+ :file_path => subtree(:new_file_path),
63
+ ) do
64
+ if override.to_sym == overriding_key[:snake_case_key].to_sym
65
+ {
66
+ overridden_key[:snake_case_key] => String(new_file_path)
67
+ }
68
+ else
69
+ {
70
+ }
71
+ end
72
+ end
73
+ end
66
74
  end
@@ -1,3 +1,5 @@
1
1
  module Figtree
2
- VERSION = "1.0.1"
2
+ # for reference see
3
+ # http://guides.rubygems.org/patterns/#semantic-versioning
4
+ VERSION = "1.2.0"
3
5
  end
@@ -10,7 +10,13 @@ module Figtree
10
10
  expect(parser.grouper).to parse('[common]')
11
11
  end
12
12
  it 'can parse comments' do
13
- expect(parser.comment).to parse('; This is a comment\n')
13
+ expect(parser.comment).to parse("; This is a comment\n")
14
+ expect(parser.comment).to parse("; last modified 1 April 2001 by John Doe\n")
15
+ comment_first = File.open('spec/support/wiki_example.ini', &:readline)
16
+ expect(parser.comment).to parse(comment_first)
17
+ end
18
+ it 'can parse comments then groups' do
19
+ expect(parser.comment_or_group).to parse("; comment\n[groop]\nassignment = present")
14
20
  end
15
21
  it 'can parse snake_case keys' do
16
22
  expect(parser.snake_case_key).to parse('basic_size_limit')
@@ -29,6 +35,13 @@ module Figtree
29
35
  it 'can parse numbers' do
30
36
  expect(parser.number).to parse("26214400")
31
37
  end
38
+ it 'can parse ip addresses' do
39
+ expect(parser.ip_address).to parse("FE80:0000:0000:0000:0202:B3FF:FE1E:8329")
40
+ expect(parser.ip_address).to parse('111.222.3.4')
41
+ expect(parser.ip_address).to parse('192.0.2.62')
42
+ expect(parser.ip_address).to_not parse('f11.222.3.4')
43
+ expect(parser.ip_address).to_not parse('111.222.3')
44
+ end
32
45
  it 'can parse booleans flexibly' do
33
46
  expect(parser.boolean).to parse("no")
34
47
  expect(parser.boolean).to parse("yes")
@@ -1,58 +1,70 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  module Figtree
4
- describe Transformer do
5
- context "can do type conversion" do
6
- let(:int_tree) do
7
- Parser.new.parse("[common]\nbasic_size_limit = 26214400\n")
8
- end
9
- let(:arr_tree) do
10
- Parser.new.parse("[http]\nparams = array,of,values\n")
11
- end
12
- let(:bool_tree) do
13
- Parser.new.parse("[ftp]\nenabled = no\n")
14
- end
15
- it 'can apply an int type conversion' do
16
- expect(Transformer.new.apply(int_tree)).to eq(
17
- [
18
- {
19
- common: Subgroup.new({basic_size_limit: 26214400})
20
- }
21
- ]
22
- )
23
- end
24
- it 'can apply an array type conversion' do
25
- expect(Transformer.new.apply(arr_tree)).to eq(
26
- [
27
- {
28
- http: Subgroup.new(params: ["array", "of", "values"])
29
- }
30
- ]
31
- )
32
- end
33
- it 'can apply a bool type conversion' do
34
- expect(Transformer.new.apply(bool_tree)).to eq(
35
- [
36
- {
37
- ftp: Subgroup.new(enabled: false)
38
- }
39
- ]
40
- )
41
- end
42
- end
43
- context "overrides by angle brackets" do
44
- let(:override_tree) do
45
- Parser.new.parse("[http]\npath = /srv/\npath<production> = /srv/var/tmp/\n")
46
- end
47
- it 'can apply an override' do
48
- expect(Transformer.new.apply(override_tree, override: :production)).to eq(
49
- [
50
- {
51
- http: Subgroup.new(path: '/srv/var/tmp/')
52
- }
53
- ]
54
- )
55
- end
56
- end
57
- end
4
+ describe Transformer do
5
+ context "can do type conversion" do
6
+ let(:int_tree) do
7
+ Parser.new.parse("[common]\nbasic_size_limit = 26214400\n")
8
+ end
9
+ let(:arr_tree) do
10
+ Parser.new.parse("[http]\nparams = array,of,values\n")
11
+ end
12
+ let(:bool_tree) do
13
+ Parser.new.parse("[ftp]\nenabled = no\n")
14
+ end
15
+ let(:ip_tree) do
16
+ Parser.new.parse("[database]\nserver=192.0.2.62")
17
+ end
18
+ it 'can apply an int type conversion' do
19
+ expect(Transformer.new.apply(int_tree)).to eq(
20
+ [
21
+ {
22
+ common: Subgroup.new({basic_size_limit: 26214400})
23
+ }
24
+ ]
25
+ )
26
+ end
27
+ it 'can apply an array type conversion' do
28
+ expect(Transformer.new.apply(arr_tree)).to eq(
29
+ [
30
+ {
31
+ http: Subgroup.new(params: ["array", "of", "values"])
32
+ }
33
+ ]
34
+ )
35
+ end
36
+ it 'can apply a bool type conversion' do
37
+ expect(Transformer.new.apply(bool_tree)).to eq(
38
+ [
39
+ {
40
+ ftp: Subgroup.new(enabled: false)
41
+ }
42
+ ]
43
+ )
44
+ end
45
+ it 'it can apply an ip address type conversion' do
46
+ expect(Transformer.new.apply(ip_tree)).to eq(
47
+ [
48
+ {
49
+ database: Subgroup.new(server: IPAddr.new("192.0.2.62"))
50
+ }
51
+ ]
52
+ )
53
+ end
54
+ end
55
+ context "overrides by angle brackets" do
56
+ let(:override_tree) do
57
+ Parser.new.parse("[http]\npath = /srv/\npath<production> = /srv/var/tmp/\n")
58
+ end
59
+ it 'can apply an override' do
60
+ expect(Transformer.new.apply(override_tree, override: :production)).to eq(
61
+ [
62
+ {
63
+ http: Subgroup.new(path: '/srv/var/tmp/')
64
+ }
65
+ ]
66
+ )
67
+ end
68
+ end
69
+ end
58
70
  end
@@ -69,13 +69,19 @@ describe Figtree do
69
69
  expect(Figtree::IniConfig.new(settings_path)).to eq(the_whole_kebab)
70
70
  end
71
71
 
72
+ it 'can parse the wiki example' do
73
+ wiki_example = Figtree::IniConfig.new('spec/support/wiki_example.ini')
74
+ expect(wiki_example.database.server).to_not be_nil
75
+ end
76
+
72
77
  context "performance" do
73
78
  it 'can parse the whole ini file quickly' do
74
79
  expect(
75
80
  Benchmark.realtime do
76
81
  Figtree::IniConfig.new(settings_path)
77
82
  end
78
- ).to be < 0.014
83
+ ).to be < 0.025
84
+ # without ip_address parsing this was under 0.014 :(
79
85
  end
80
86
  end
81
87
 
@@ -5,6 +5,7 @@ require 'pry'
5
5
  require 'ostruct'
6
6
  require 'parslet'
7
7
  require 'parslet/rig/rspec'
8
+ require 'parslet/convenience'
8
9
 
9
10
  require 'simplecov'
10
11
  SimpleCov.start
@@ -0,0 +1,10 @@
1
+ ; last modified 1 April 2001 by John Doe
2
+ [owner]
3
+ name="John Doe"
4
+ organization="Acme Widgets Inc."
5
+
6
+ [database]
7
+ ; use IP address in case network name resolution is not working
8
+ server=192.0.2.62
9
+ port=143
10
+ file="payroll.dat"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: figtree
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Moore-Niemi
@@ -94,6 +94,7 @@ extra_rdoc_files: []
94
94
  files:
95
95
  - ".gitignore"
96
96
  - ".ruby-version"
97
+ - CHANGELOG.md
97
98
  - Gemfile
98
99
  - Gemfile.lock
99
100
  - MIT-LICENSE
@@ -101,6 +102,7 @@ files:
101
102
  - figtree.gemspec
102
103
  - lib/figtree.rb
103
104
  - lib/figtree/ini_config.rb
105
+ - lib/figtree/ip_rules.rb
104
106
  - lib/figtree/parser.rb
105
107
  - lib/figtree/transformer.rb
106
108
  - lib/figtree/version.rb
@@ -111,6 +113,7 @@ files:
111
113
  - spec/support/settings.conf
112
114
  - spec/support/unparseable_settings.conf
113
115
  - spec/support/untransformable_settings.conf
116
+ - spec/support/wiki_example.ini
114
117
  homepage: https://github.com/mooreniemi/figtree
115
118
  licenses:
116
119
  - MIT
@@ -144,4 +147,5 @@ test_files:
144
147
  - spec/support/settings.conf
145
148
  - spec/support/unparseable_settings.conf
146
149
  - spec/support/untransformable_settings.conf
150
+ - spec/support/wiki_example.ini
147
151
  has_rdoc: