archieml 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.gitmodules +4 -0
- data/README.md +2 -1
- data/lib/archieml/loader.rb +81 -38
- data/lib/archieml/version.rb +1 -1
- data/spec/lib/archieml/loader_spec.rb +12 -564
- data/spec/spec_helper.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e1769d7385a90df46fb88685e1be8cef959943c1
|
4
|
+
data.tar.gz: c92bea5829cc1729197ee1035f7398256b3205c8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 045a8a57c8676be985fd6194c708f09eb157b93ea97ef032d3a88bfe0e4d6431f94924c25e66e0e9265bef9fd54ce07a47a3a2bdbc87e94bbee8d395abc81d00
|
7
|
+
data.tar.gz: 7bbd0d896a7ae3348814ab36ec6cbe795739ab9543b1971542671d21b629b26b1c15f32b4e7594ff83898c80dec090e3b8bd12e317b92a1023d0748d9742b19d
|
data/.gitignore
CHANGED
data/.gitmodules
ADDED
data/README.md
CHANGED
@@ -4,7 +4,7 @@ Parse Archie Markup Language (ArchieML) documents into Ruby Hashes.
|
|
4
4
|
|
5
5
|
Read about the ArchieML specification at [archieml.org](http://archieml.org).
|
6
6
|
|
7
|
-
The current version is `v0.
|
7
|
+
The current version is `v0.2.0`.
|
8
8
|
|
9
9
|
## Installation
|
10
10
|
|
@@ -173,5 +173,6 @@ There is a full test suite using rspec. `bundle install`, and then `rspec` to ex
|
|
173
173
|
|
174
174
|
## Changelog
|
175
175
|
|
176
|
+
* `0.2.0` - Updated to support an updated ArchieML spec: [2015-05-09](http://archieml.org/spec/1.0/CR-20150509.html). Adds support for nested arrays.
|
176
177
|
* `0.1.1` - More consistent handling of newlines. Fixed bugs around detecting the scope of multi-line values.
|
177
178
|
* `0.1.0` - Initial release supporting the first version of the ArchieML spec, published [2015-03-06](http://archieml.org/spec/1.0/CR-20150306.html).
|
data/lib/archieml/loader.rb
CHANGED
@@ -7,29 +7,36 @@ module Archieml
|
|
7
7
|
ARRAY_ELEMENT = /^\s*\*[ \t\r]*(.*(?:\n|\r|$))/
|
8
8
|
SCOPE_PATTERN = /^\s*(\[|\{)[ \t\r]*([A-Za-z0-9\-_\.]*)[ \t\r]*(?:\]|\}).*?(\n|\r|$)/
|
9
9
|
|
10
|
-
def initialize
|
10
|
+
def initialize(options = {})
|
11
11
|
@data = @scope = {}
|
12
12
|
|
13
|
-
@
|
13
|
+
@stack = []
|
14
|
+
@stack_scope = nil
|
15
|
+
|
16
|
+
@buffer_scope = @buffer_key = nil
|
14
17
|
@buffer_string = ''
|
15
18
|
|
16
|
-
@is_skipping
|
19
|
+
@is_skipping = false
|
17
20
|
@done_parsing = false
|
18
21
|
|
19
|
-
|
22
|
+
@default_options = {
|
23
|
+
comments: false
|
24
|
+
}.merge(options)
|
20
25
|
end
|
21
26
|
|
22
|
-
def load(stream)
|
27
|
+
def load(stream, options = {})
|
28
|
+
@options = @default_options.merge(options)
|
29
|
+
|
23
30
|
stream.each_line do |line|
|
24
31
|
return @data if @done_parsing
|
25
32
|
|
26
33
|
if match = line.match(COMMAND_KEY)
|
27
34
|
self.parse_command_key(match[1].downcase)
|
28
35
|
|
29
|
-
elsif !@is_skipping && (match = line.match(START_KEY)) && (!@
|
36
|
+
elsif !@is_skipping && (match = line.match(START_KEY)) && (!@stack_scope || @stack_scope[:array_type] != :simple)
|
30
37
|
self.parse_start_key(match[1], match[2] || '')
|
31
38
|
|
32
|
-
elsif !@is_skipping && (match = line.match(ARRAY_ELEMENT)) && @
|
39
|
+
elsif !@is_skipping && (match = line.match(ARRAY_ELEMENT)) && @stack_scope && @stack_scope[:array_type] != :complex
|
33
40
|
self.parse_array_element(match[1])
|
34
41
|
|
35
42
|
elsif !@is_skipping && match = line.match(SCOPE_PATTERN)
|
@@ -47,37 +54,27 @@ module Archieml
|
|
47
54
|
def parse_start_key(key, rest_of_line)
|
48
55
|
self.flush_buffer!
|
49
56
|
|
50
|
-
|
51
|
-
@array_type ||= 'complex'
|
52
|
-
|
53
|
-
# Ignore complex keys inside simple arrays
|
54
|
-
return if @array_type == 'simple'
|
55
|
-
|
56
|
-
if [nil, key].include?(@array_first_key)
|
57
|
-
@array << (@scope = {})
|
58
|
-
end
|
59
|
-
|
60
|
-
@array_first_key ||= key
|
61
|
-
end
|
57
|
+
self.increment_array_element(key)
|
62
58
|
|
63
59
|
@buffer_key = key
|
64
60
|
@buffer_string = rest_of_line
|
65
61
|
|
66
62
|
self.flush_buffer_into(key, replace: true)
|
63
|
+
@buffer_key = key
|
67
64
|
end
|
68
65
|
|
69
66
|
def parse_array_element(value)
|
70
67
|
self.flush_buffer!
|
71
68
|
|
72
|
-
@array_type ||=
|
69
|
+
@stack_scope[:array_type] ||= :simple
|
73
70
|
|
74
71
|
# Ignore simple array elements inside complex arrays
|
75
|
-
return if @array_type ==
|
72
|
+
return if @stack_scope[:array_type] == :complex
|
76
73
|
|
77
|
-
@array << ''
|
78
|
-
@buffer_key = @array
|
74
|
+
@stack_scope[:array] << ''
|
79
75
|
@buffer_string = value
|
80
|
-
self.flush_buffer_into(@array, replace: true)
|
76
|
+
self.flush_buffer_into(@stack_scope[:array], replace: true)
|
77
|
+
@buffer_key = @stack_scope[:array]
|
81
78
|
end
|
82
79
|
|
83
80
|
def parse_command_key(command)
|
@@ -99,39 +96,86 @@ module Archieml
|
|
99
96
|
when "endskip"
|
100
97
|
@is_skipping = false
|
101
98
|
end
|
99
|
+
|
100
|
+
self.flush_buffer!
|
102
101
|
end
|
103
102
|
|
104
103
|
def parse_scope(scope_type, scope_key)
|
105
104
|
self.flush_buffer!
|
106
|
-
self.flush_scope!
|
107
105
|
|
108
106
|
if scope_key == ''
|
109
|
-
|
107
|
+
case scope_type
|
108
|
+
when '{'
|
109
|
+
@scope = @data
|
110
|
+
@stack_scope = nil
|
111
|
+
@stack = []
|
112
|
+
when '['
|
113
|
+
# Move up a level
|
114
|
+
if last_stack_item = @stack.pop
|
115
|
+
@scope = last_stack_item[:scope] || @data
|
116
|
+
@stack_scope = @stack.last
|
117
|
+
end
|
118
|
+
end
|
110
119
|
|
111
120
|
elsif %w([ {).include?(scope_type)
|
121
|
+
nesting = false
|
112
122
|
key_scope = @data
|
123
|
+
|
124
|
+
if scope_key.match(/^\./)
|
125
|
+
scope_key = scope_key[1..-1]
|
126
|
+
self.increment_array_element(scope_key)
|
127
|
+
nesting = true
|
128
|
+
key_scope = @scope if @stack_scope
|
129
|
+
end
|
130
|
+
|
113
131
|
key_bits = scope_key.split('.')
|
114
132
|
key_bits[0...-1].each do |bit|
|
115
133
|
key_scope = key_scope[bit] ||= {}
|
116
134
|
end
|
117
135
|
|
118
136
|
if scope_type == '['
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
137
|
+
stack_scope_item = {
|
138
|
+
array: key_scope[key_bits.last] = [],
|
139
|
+
array_type: nil,
|
140
|
+
array_first_key: nil,
|
141
|
+
scope: @scope
|
142
|
+
}
|
143
|
+
|
144
|
+
if nesting
|
145
|
+
@stack << stack_scope_item
|
146
|
+
else
|
147
|
+
@stack = [stack_scope_item]
|
124
148
|
end
|
149
|
+
@stack_scope = @stack.last
|
125
150
|
|
126
151
|
elsif scope_type == '{'
|
127
|
-
@scope = key_scope[key_bits.last]
|
152
|
+
@scope = key_scope[key_bits.last] = key_scope[key_bits.last].is_a?(Hash) ? key_scope[key_bits.last] : {}
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def increment_array_element(key)
|
158
|
+
# Special handling for arrays. If this is the start of the array, remember
|
159
|
+
# which key was encountered first. If this is a duplicate encounter of
|
160
|
+
# that key, start a new object.
|
161
|
+
|
162
|
+
if @stack_scope && @stack_scope[:array]
|
163
|
+
# If we're within a simple array, ignore
|
164
|
+
@stack_scope[:array_type] ||= :complex
|
165
|
+
return if @stack_scope[:array_type] == :simple
|
166
|
+
|
167
|
+
# array_first_key may be either another key, or nil
|
168
|
+
if @stack_scope[:array_first_key] == nil || @stack_scope[:array_first_key] == key
|
169
|
+
@stack_scope[:array] << (@scope = {})
|
128
170
|
end
|
171
|
+
@stack_scope[:array_first_key] ||= key
|
129
172
|
end
|
130
173
|
end
|
131
174
|
|
132
175
|
def flush_buffer!
|
133
176
|
result = @buffer_string.dup
|
134
177
|
@buffer_string = ''
|
178
|
+
@buffer_key = nil
|
135
179
|
return result
|
136
180
|
end
|
137
181
|
|
@@ -163,10 +207,6 @@ module Archieml
|
|
163
207
|
end
|
164
208
|
end
|
165
209
|
|
166
|
-
def flush_scope!
|
167
|
-
@array = @array_type = @array_first_key = @buffer_key = nil
|
168
|
-
end
|
169
|
-
|
170
210
|
# type can be either :replace or :append.
|
171
211
|
# If it's :replace, then the string is assumed to be the first line of a
|
172
212
|
# value, and no escaping takes place.
|
@@ -174,8 +214,11 @@ module Archieml
|
|
174
214
|
# by prepending the line with a backslash.
|
175
215
|
# (:, [, {, *, \) surrounding the first token of any line.
|
176
216
|
def format_value(value, type)
|
177
|
-
|
178
|
-
|
217
|
+
# Deprecated
|
218
|
+
if @options[:comments]
|
219
|
+
value.gsub!(/(?:^\\)?\[[^\[\]\n\r]*\](?!\])/, '') # remove comments
|
220
|
+
value.gsub!(/\[\[([^\[\]\n\r]*)\]\]/, '[\1]') # [[]] => []
|
221
|
+
end
|
179
222
|
|
180
223
|
if type == :append
|
181
224
|
value.gsub!(/^(\s*)\\/, '\1')
|
data/lib/archieml/version.rb
CHANGED
@@ -1,573 +1,21 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Archieml::Loader do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
allow(@loader).to receive(:parse_start_key).with(any_args).and_call_original
|
8
|
-
allow(@loader).to receive(:parse_command_key).with(any_args).and_call_original
|
9
|
-
end
|
10
|
-
|
11
|
-
describe "parsing values" do
|
12
|
-
it "parses key value pairs" do
|
13
|
-
@loader.load("key:value")['key'].should == 'value'
|
14
|
-
end
|
15
|
-
it "ignores spaces on either side of the key" do
|
16
|
-
@loader.load(" key :value")['key'].should == 'value'
|
17
|
-
end
|
18
|
-
it "ignores tabs on either side of the key" do
|
19
|
-
@loader.load("\t\tkey\t\t:value")['key'].should == 'value'
|
20
|
-
end
|
21
|
-
it "ignores spaces on either side of the value" do
|
22
|
-
@loader.load("key: value ")['key'].should == 'value'
|
23
|
-
end
|
24
|
-
it "ignores tabs on either side of the value" do
|
25
|
-
@loader.load("key:\t\tvalue\t\t")['key'].should == 'value'
|
26
|
-
end
|
27
|
-
it "dupliate keys are assigned the last given value" do
|
28
|
-
@loader.load("key:value\nkey:newvalue")['key'].should == 'newvalue'
|
29
|
-
end
|
30
|
-
it "allows non-letter characters at the start of values" do
|
31
|
-
@loader.load("key::value")['key'].should == ':value'
|
32
|
-
end
|
33
|
-
it "keys are case sensitive" do
|
34
|
-
@loader.load("key:value\nKey:Value").keys.should == ['key', 'Key']
|
35
|
-
end
|
36
|
-
it "non-keys don't affect parsing" do
|
37
|
-
@loader.load("other stuff\nkey:value\nother stuff")['key'].should == 'value'
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
describe "valid keys" do
|
42
|
-
|
43
|
-
it "letters, numbers, dashes and underscores are valid key components" do
|
44
|
-
@loader.load("a-_1:value")['a-_1'].should == 'value'
|
45
|
-
end
|
46
|
-
it "spaces are not allowed in keys" do
|
47
|
-
@loader.load("k ey:value").keys.length.should == 0
|
48
|
-
end
|
49
|
-
it "symbols are not allowed in keys" do
|
50
|
-
@loader.load("k&ey:value").keys.length.should == 0
|
51
|
-
end
|
52
|
-
it "keys can be nested using dot-notation" do
|
53
|
-
@loader.load("scope.key:value")['scope']['key'].should == 'value'
|
54
|
-
end
|
55
|
-
it "earlier keys within scopes aren't deleted when using dot-notation" do
|
56
|
-
@loader.load("scope.key:value\nscope.otherkey:value")['scope']['key'].should == 'value'
|
57
|
-
@loader.load("scope.key:value\nscope.otherkey:value")['scope']['otherkey'].should == 'value'
|
58
|
-
end
|
59
|
-
it "the value of key that used to be a string should be replaced with an object if necessary" do
|
60
|
-
@loader.load("scope.level:value\nscope.level.level:value")['scope']['level']['level'].should == 'value'
|
61
|
-
end
|
62
|
-
it "the value of key that used to be a parent object should be replaced with a string if necessary" do
|
63
|
-
@loader.load("scope.level.level:value\nscope.level:value")['scope']['level'].should == 'value'
|
64
|
-
end
|
65
|
-
|
66
|
-
end
|
67
|
-
|
68
|
-
describe "valid values" do
|
69
|
-
|
70
|
-
it "HTML is allowed" do
|
71
|
-
@loader.load("key:<strong>value</strong>")['key'].should == '<strong>value</strong>'
|
72
|
-
end
|
73
|
-
|
74
|
-
end
|
75
|
-
|
76
|
-
describe "skip" do
|
77
|
-
|
78
|
-
it "ignores spaces on either side of :skip" do
|
79
|
-
expect(@loader).to receive(:parse_command_key).with('skip').once
|
80
|
-
@loader.load(" :skip \nkey:value\n:endskip").keys.length.should == 0
|
81
|
-
end
|
82
|
-
it "ignores tabs on either side of :skip" do
|
83
|
-
expect(@loader).to receive(:parse_command_key).with('skip').once
|
84
|
-
@loader.load("\t\t:skip\t\t\nkey:value\n:endskip").keys.length.should == 0
|
85
|
-
end
|
86
|
-
it "ignores spaces on either side of :endskip" do
|
87
|
-
expect(@loader).to receive(:parse_command_key).with('endskip').once
|
88
|
-
@loader.load(":skip\nkey:value\n :endskip ").keys.length.should == 0
|
89
|
-
end
|
90
|
-
it "ignores tabs on either side of :endskip" do
|
91
|
-
expect(@loader).to receive(:parse_command_key).with('endskip').once
|
92
|
-
@loader.load(":skip\nkey:value\n\t\t:endskip\t\t").keys.length.should == 0
|
93
|
-
end
|
94
|
-
it "starts parsing again after :endskip" do
|
95
|
-
expect(@loader).to receive(:parse_start_key).with('key', 'value').once
|
96
|
-
@loader.load(":skip\n:endskip\nkey:value").keys.length.should == 1
|
97
|
-
end
|
98
|
-
it ":skip and :endskip are case insensitive" do
|
99
|
-
expect(@loader).to receive(:parse_command_key).with('skip').once
|
100
|
-
expect(@loader).to receive(:parse_command_key).with('endskip').once
|
101
|
-
@loader.load(":sKiP\nkey:value\n:eNdSkIp").keys.length.should == 0
|
102
|
-
end
|
103
|
-
it "parse :skip as a special command even if more is appended to word" do
|
104
|
-
expect(@loader).to receive(:parse_command_key).with('skip')
|
105
|
-
@loader.load(":skipthis\nkey:value\n:endskip").keys.length.should == 0
|
106
|
-
end
|
107
|
-
it "ignores all content on line after :skip + space" do
|
108
|
-
expect(@loader).to receive(:parse_command_key).with('skip').once
|
109
|
-
expect(@loader).to_not receive(:parse_start_key).with('key', 'value')
|
110
|
-
@loader.load(":skip this text \nkey:value\n:endskip").keys.length.should == 0
|
111
|
-
end
|
112
|
-
it "ignores all content on line after :skip + tab" do
|
113
|
-
expect(@loader).to receive(:parse_command_key).with('skip').once
|
114
|
-
expect(@loader).to_not receive(:parse_start_key).with('key', 'value')
|
115
|
-
@loader.load(":skip\tthis text\t\t\nkey:value\n:endskip").keys.length.should == 0
|
116
|
-
end
|
117
|
-
it "parse :endskip as a special command even if more is appended to word" do
|
118
|
-
expect(@loader).to receive(:parse_command_key).with('endskip')
|
119
|
-
@loader.load(":skip\n:endskiptheabove\nkey:value").keys.length.should == 1
|
120
|
-
end
|
121
|
-
it "ignores all content on line after :endskip + space" do
|
122
|
-
expect(@loader).to receive(:parse_command_key).with('endskip').once
|
123
|
-
expect(@loader).to receive(:parse_start_key).with('key', 'value').once
|
124
|
-
@loader.load(":skip\n:endskip the above\nkey:value").keys.length.should == 1
|
125
|
-
end
|
126
|
-
it "ignores all content on line after :endskip + tab" do
|
127
|
-
expect(@loader).to receive(:parse_command_key).with('endskip').once
|
128
|
-
expect(@loader).to receive(:parse_start_key).with('key', 'value').once
|
129
|
-
@loader.load(":skip\n:endskip\tthe above\nkey:value").keys.length.should == 1
|
130
|
-
end
|
131
|
-
it "does not parse :end as an :endskip" do
|
132
|
-
expect(@loader).to_not receive(:parse_command_key).with('endskip')
|
133
|
-
@loader.load(":skip\n:end\tthe above\nkey:value").keys.length.should == 0
|
134
|
-
end
|
135
|
-
it "ignores keys within a skip block" do
|
136
|
-
expect(@loader).to_not receive(:parse_start_key).with('other', 'value')
|
137
|
-
@loader.load("key1:value1\n:skip\nother:value\n\n:endskip\n\nkey2:value2").keys.should == ['key1', 'key2']
|
138
|
-
end
|
139
|
-
|
140
|
-
end
|
141
|
-
|
142
|
-
describe "ignore" do
|
143
|
-
|
144
|
-
it "text before ':ignore' should be included" do
|
145
|
-
@loader.load("key:value\n:ignore")['key'].should == 'value'
|
146
|
-
end
|
147
|
-
it "text after ':ignore' should be ignored" do
|
148
|
-
expect(@loader).to_not receive(:parse_start_key)
|
149
|
-
@loader.load(":ignore\nkey:value").keys.length.should == 0
|
150
|
-
end
|
151
|
-
it "':ignore' is case insensitive" do
|
152
|
-
expect(@loader).to receive(:parse_command_key).with('ignore').once
|
153
|
-
@loader.load(":iGnOrE\nkey:value").keys.length.should == 0
|
154
|
-
end
|
155
|
-
it "ignores spaces on either side of :ignore" do
|
156
|
-
expect(@loader).to receive(:parse_command_key).with('ignore').once
|
157
|
-
@loader.load(":iGnOrE\nkey:value").keys.length.should == 0
|
158
|
-
@loader.load(" :ignore \nkey:value")
|
159
|
-
end
|
160
|
-
it "ignores tabs on either side of :ignore" do
|
161
|
-
expect(@loader).to receive(:parse_command_key).with('ignore').once
|
162
|
-
@loader.load(":iGnOrE\nkey:value").keys.length.should == 0
|
163
|
-
@loader.load("\t\t:ignore\t\t\nkey:value")
|
164
|
-
end
|
165
|
-
it "parses :ignore as a special command even if more is appended to word" do
|
166
|
-
expect(@loader).to receive(:parse_command_key).with('ignore')
|
167
|
-
@loader.load(":ignorethis\nkey:value").keys.length.should == 0
|
168
|
-
end
|
169
|
-
it "ignores all content on line after :ignore + space" do
|
170
|
-
expect(@loader).to receive(:parse_command_key).with('ignore').once
|
171
|
-
@loader.load(":iGnOrE\nkey:value").keys.length.should == 0
|
172
|
-
@loader.load(":ignore the below\nkey:value")
|
173
|
-
end
|
174
|
-
it "ignores all content on line after :ignore + tab" do
|
175
|
-
expect(@loader).to receive(:parse_command_key).with('ignore').once
|
176
|
-
@loader.load(":iGnOrE\nkey:value").keys.length.should == 0
|
177
|
-
@loader.load(":ignore\tthe below\nkey:value")
|
178
|
-
end
|
179
|
-
|
180
|
-
end
|
181
|
-
|
182
|
-
describe "multi line values" do
|
183
|
-
|
184
|
-
it "adds additional lines to value if followed by an ':end'" do
|
185
|
-
@loader.load("key:value\nextra\n:end")['key'].should == "value\nextra"
|
186
|
-
end
|
187
|
-
it "':end' is case insensitive" do
|
188
|
-
expect(@loader).to receive(:parse_command_key).with('end').once
|
189
|
-
@loader.load("key:value\nextra\n:EnD")
|
190
|
-
end
|
191
|
-
it "preserves blank lines and whitespace lines in the middle of content" do
|
192
|
-
@loader.load("key:value\n\n\t \nextra\n:end")['key'].should == "value\n\n\t \nextra"
|
193
|
-
end
|
194
|
-
it "doesn't preserve whitespace at the end of the key" do
|
195
|
-
@loader.load("key:value\nextra\t \n:end")['key'].should == "value\nextra"
|
196
|
-
end
|
197
|
-
it "preserves whitespace at the end of the original line" do
|
198
|
-
@loader.load("key:value\t \nextra\n:end")['key'].should == "value\t \nextra"
|
199
|
-
end
|
200
|
-
it "ignores whitespace and newlines before the ':end'" do
|
201
|
-
@loader.load("key:value\nextra\n \n\t\n:end")['key'].should == "value\nextra"
|
202
|
-
end
|
203
|
-
it "ignores spaces on either side of :end" do
|
204
|
-
expect(@loader).to receive(:parse_command_key).with('end').once
|
205
|
-
@loader.load("key:value\nextra\n :end ")
|
206
|
-
end
|
207
|
-
it "ignores tabs on either side of :end" do
|
208
|
-
expect(@loader).to receive(:parse_command_key).with('end').once
|
209
|
-
@loader.load("key:value\nextra\n\t\t:end\t\t")
|
210
|
-
end
|
211
|
-
it "parses :end as a special command even if more is appended to word" do
|
212
|
-
expect(@loader).to receive(:parse_command_key).with('end')
|
213
|
-
@loader.load("key:value\nextra\n:endthis")['key'].should == "value\nextra"
|
214
|
-
end
|
215
|
-
it "does not parse :endskip as an :end" do
|
216
|
-
expect(@loader).to_not receive(:parse_command_key).with('end')
|
217
|
-
@loader.load("key:value\nextra\n:endskip")['key'].should == "value"
|
218
|
-
end
|
219
|
-
it "ordinary text that starts with a colon is included" do
|
220
|
-
@loader.load("key:value\n:notacommand\n:end")['key'].should == "value\n:notacommand"
|
221
|
-
end
|
222
|
-
it "ignores all content on line after :end + space" do
|
223
|
-
expect(@loader).to receive(:parse_command_key).with('end').once
|
224
|
-
@loader.load("key:value\nextra\n:end this")['key'].should == "value\nextra"
|
225
|
-
end
|
226
|
-
it "ignores all content on line after :end + tab" do
|
227
|
-
expect(@loader).to receive(:parse_command_key).with('end').once
|
228
|
-
@loader.load("key:value\nextra\n:end\tthis")['key'].should == "value\nextra"
|
229
|
-
end
|
230
|
-
it "doesn't escape colons on first line" do
|
231
|
-
@loader.load("key::value\n:end")['key'].should == ":value"
|
232
|
-
@loader.load("key:\\:value\n:end")['key'].should == "\\:value"
|
233
|
-
end
|
234
|
-
it "does not allow escaping keys" do
|
235
|
-
@loader.load("key:value\nkey2\\:value\n:end")['key'].should == "value\nkey2\\:value"
|
236
|
-
end
|
237
|
-
it "allows escaping key lines with a leading backslash" do
|
238
|
-
@loader.load("key:value\n\\key2:value\n:end")['key'].should == "value\nkey2:value"
|
239
|
-
end
|
240
|
-
it "allows escaping commands at the beginning of lines" do
|
241
|
-
@loader.load("key:value\n\\:end\n:end")['key'].should == "value\n:end"
|
242
|
-
end
|
243
|
-
it "allows escaping commands with extra text at the beginning of lines" do
|
244
|
-
@loader.load("key:value\n\\:endthis\n:end")['key'].should == "value\n:endthis"
|
245
|
-
end
|
246
|
-
it "allows escaping of non-commandc at the beginning of lines" do
|
247
|
-
@loader.load("key:value\n\\:notacommand\n:end")['key'].should == "value\n:notacommand"
|
248
|
-
end
|
249
|
-
it "allows simple array style lines" do
|
250
|
-
@loader.load("key:value\n* value\n:end")['key'].should == "value\n* value"
|
251
|
-
end
|
252
|
-
it "escapes '*' within multi-line values when not in a simple array" do
|
253
|
-
@loader.load("key:value\n\\* value\n:end")['key'].should == "value\n* value"
|
254
|
-
end
|
255
|
-
it "allows escaping scope keys at the beginning of lines" do
|
256
|
-
@loader.load("key:value\n\\{scope}\n:end")['key'].should == "value\n{scope}"
|
257
|
-
@loader.load("key:value\n\\[comment]\n:end")['key'].should == "value"
|
258
|
-
@loader.load("key:value\n\\[[array]]\n:end")['key'].should == "value\n[array]"
|
259
|
-
end
|
260
|
-
it "arrays within a multi-line value breaks up the value" do
|
261
|
-
@loader.load("key:value\ntext\n[array]\nmore text\n:end")['key'].should == "value"
|
262
|
-
end
|
263
|
-
it "objects within a multi-line value breaks up the value" do
|
264
|
-
@loader.load("key:value\ntext\n{scope}\nmore text\n:end")['key'].should == "value"
|
265
|
-
end
|
266
|
-
it "bullets within a multi-line value do not break up the value" do
|
267
|
-
@loader.load("key:value\ntext\n* value\nmore text\n:end")['key'].should == "value\ntext\n* value\nmore text"
|
268
|
-
end
|
269
|
-
it "skips within a multi-line value do not break up the value" do
|
270
|
-
@loader.load("key:value\ntext\n:skip\n:endskip\nmore text\n:end")['key'].should == "value\ntext\nmore text"
|
271
|
-
end
|
272
|
-
it "allows escaping initial backslash at the beginning of lines" do
|
273
|
-
@loader.load("key:value\n\\\\:end\n:end")['key'].should == "value\n\\:end"
|
274
|
-
end
|
275
|
-
it "escapes only one initial backslash" do
|
276
|
-
@loader.load("key:value\n\\\\\\:end\n:end")['key'].should == "value\n\\\\:end"
|
277
|
-
end
|
278
|
-
it "allows escaping multiple lines in a value" do
|
279
|
-
@loader.load("key:value\n\\:end\n\\:ignore\n\\:endskip\n\\:skip\n:end'")['key'].should == "value\n:end\n:ignore\n:endskip\n:skip"
|
280
|
-
end
|
281
|
-
it "doesn't escape colons after beginning of lines" do
|
282
|
-
@loader.load("key:value\nLorem key2\\:value\n:end")['key'].should == "value\nLorem key2\\:value"
|
283
|
-
end
|
284
|
-
|
285
|
-
end
|
286
|
-
|
287
|
-
describe "scopes" do
|
288
|
-
|
289
|
-
it "{scope} creates an empty object at 'scope'" do
|
290
|
-
@loader.load("{scope}")['scope'].class.should == Hash
|
291
|
-
end
|
292
|
-
it "ignores spaces on either side of {scope}" do
|
293
|
-
expect(@loader).to receive(:parse_scope).with('{', 'scope').once
|
294
|
-
@loader.load(" {scope} ")
|
295
|
-
end
|
296
|
-
it "ignores tabs on either side of {scope}" do
|
297
|
-
expect(@loader).to receive(:parse_scope).with('{', 'scope').once
|
298
|
-
@loader.load("\t\t{scope}\t\t")['scope'].should == {}
|
299
|
-
end
|
300
|
-
it "ignores text after {scope}" do
|
301
|
-
expect(@loader).to receive(:parse_scope).with('{', 'scope').once
|
302
|
-
@loader.load("{scope}a")['scope'].should == {}
|
303
|
-
end
|
304
|
-
it "ignores spaces on either side of {scope} variable name" do
|
305
|
-
expect(@loader).to receive(:parse_scope).with('{', 'scope').once
|
306
|
-
@loader.load("{ scope }")['scope'].should == {}
|
307
|
-
end
|
308
|
-
it "ignores tabs on either side of {scope} variable name" do
|
309
|
-
expect(@loader).to receive(:parse_scope).with('{', 'scope').once
|
310
|
-
@loader.load("{\t\tscope\t\t}")['scope'].should == {}
|
311
|
-
end
|
312
|
-
it "items before a {scope} are not namespaced" do
|
313
|
-
@loader.load("key:value\n{scope}")['key'].should == 'value'
|
314
|
-
end
|
315
|
-
it "items after a {scope} are namespaced" do
|
316
|
-
@loader.load("{scope}\nkey:value")['key'].should == nil
|
317
|
-
@loader.load("{scope}\nkey:value")['scope']['key'].should == 'value'
|
318
|
-
end
|
319
|
-
it "scopes can be nested using dot-notaion" do
|
320
|
-
@loader.load("{scope.scope}\nkey:value")['scope']['scope']['key'].should == 'value'
|
321
|
-
end
|
322
|
-
it "scopes can be reopened" do
|
323
|
-
@loader.load("{scope}\nkey:value\n{}\n{scope}\nother:value")['scope'].keys.should =~ ["key", "other"]
|
324
|
-
end
|
325
|
-
it "scopes do not overwrite existing values" do
|
326
|
-
@loader.load("{scope.scope}\nkey:value\n{scope.otherscope}key:value")['scope']['scope']['key'].should == 'value'
|
327
|
-
end
|
328
|
-
it "{} resets to the global scope" do
|
329
|
-
expect(@loader).to receive(:parse_scope).with('{', '').once
|
330
|
-
@loader.load("{scope}\n{}\nkey:value")['key'].should == 'value'
|
331
|
-
end
|
332
|
-
it "ignore spaces inside {}" do
|
333
|
-
expect(@loader).to receive(:parse_scope).with('{', '').once
|
334
|
-
@loader.load("{scope}\n{ }\nkey:value")['key'].should == 'value'
|
335
|
-
end
|
336
|
-
it "ignore tabs inside {}" do
|
337
|
-
expect(@loader).to receive(:parse_scope).with('{', '').once
|
338
|
-
@loader.load("{scope}\n{\t\t}\nkey:value")['key'].should == 'value'
|
339
|
-
end
|
340
|
-
it "ignore spaces on either side of {}" do
|
341
|
-
expect(@loader).to receive(:parse_scope).with('{', '').once
|
342
|
-
@loader.load("{scope}\n {} \nkey:value")['key'].should == 'value'
|
343
|
-
end
|
344
|
-
it "ignore tabs on either side of {}" do
|
345
|
-
expect(@loader).to receive(:parse_scope).with('{', '').once
|
346
|
-
@loader.load("{scope}\n\t\t{}\t\t\nkey:value")['key'].should == 'value'
|
347
|
-
end
|
348
|
-
|
349
|
-
end
|
350
|
-
|
351
|
-
describe "arrays" do
|
352
|
-
|
353
|
-
it "[array] creates an empty array at 'array'" do
|
354
|
-
@loader.load("[array]")['array'].should == []
|
355
|
-
end
|
356
|
-
it "ignores spaces on either side of [array]" do
|
357
|
-
expect(@loader).to receive(:parse_scope).with('[', 'array').once
|
358
|
-
@loader.load(" [array] ")
|
359
|
-
end
|
360
|
-
it "ignores tabs on either side of [array]" do
|
361
|
-
expect(@loader).to receive(:parse_scope).with('[', 'array').once
|
362
|
-
@loader.load("\t\t[array]\t\t")
|
363
|
-
end
|
364
|
-
it "ignores text after [array]" do
|
365
|
-
expect(@loader).to receive(:parse_scope).with('[', 'array').once
|
366
|
-
@loader.load("[array]a")['array'].should == []
|
367
|
-
end
|
368
|
-
it "ignores spaces on either side of [array] variable name" do
|
369
|
-
expect(@loader).to receive(:parse_scope).with('[', 'array').once
|
370
|
-
@loader.load("[ array ]")
|
371
|
-
end
|
372
|
-
it "ignores tabs on either side of [array] variable name" do
|
373
|
-
expect(@loader).to receive(:parse_scope).with('[', 'array').once
|
374
|
-
@loader.load("[\t\tarray\t\t]")
|
375
|
-
end
|
376
|
-
it "arrays can be nested using dot-notaion" do
|
377
|
-
@loader.load("[scope.array]")['scope']['array'].should == []
|
378
|
-
end
|
379
|
-
it "array values can be nested using dot-notaion" do
|
380
|
-
@loader.load("[array]\nscope.key: value\nscope.key: value")['array'].should == [{'scope' => {'key' => 'value'}}, {'scope' => {'key' => 'value'}}]
|
381
|
-
end
|
382
|
-
it "[] resets to the global scope" do
|
383
|
-
@loader.load("[array]\n[]\nkey:value")['key'].should == 'value'
|
384
|
-
end
|
385
|
-
it "ignore spaces inside []" do
|
386
|
-
expect(@loader).to receive(:parse_scope).with('[', '').once
|
387
|
-
@loader.load("[array]\n[ ]\nkey:value")['key'].should == 'value'
|
388
|
-
end
|
389
|
-
it "ignore tabs inside []" do
|
390
|
-
expect(@loader).to receive(:parse_scope).with('[', '').once
|
391
|
-
@loader.load("[array]\n[\t\t]\nkey:value")['key'].should == 'value'
|
392
|
-
end
|
393
|
-
it "ignore spaces on either side of []" do
|
394
|
-
expect(@loader).to receive(:parse_scope).with('[', '').once
|
395
|
-
@loader.load("[array]\n [] \nkey:value")['key'].should == 'value'
|
396
|
-
end
|
397
|
-
it "ignore tabs on either side of []" do
|
398
|
-
expect(@loader).to receive(:parse_scope).with('[', '').once
|
399
|
-
@loader.load("[array]\n\t\t[]\t\t\nkey:value")['key'].should == 'value'
|
400
|
-
end
|
401
|
-
|
402
|
-
end
|
403
|
-
|
404
|
-
describe "simple arrays" do
|
405
|
-
|
406
|
-
it "creates a simple array when an '*' is encountered first" do
|
407
|
-
@loader.load("[array]\n*Value")['array'].first.should == 'Value'
|
408
|
-
end
|
409
|
-
it "ignores spaces on either side of '*'" do
|
410
|
-
@loader.load("[array]\n * Value")['array'].first.should == 'Value'
|
411
|
-
end
|
412
|
-
it "ignores tabs on either side of '*'" do
|
413
|
-
@loader.load("[array]\n\t\t*\t\tValue")['array'].first.should == 'Value'
|
414
|
-
end
|
415
|
-
it "adds multiple elements" do
|
416
|
-
@loader.load("[array]\n*Value1\n*Value2")['array'].should == ['Value1', 'Value2']
|
417
|
-
end
|
418
|
-
it "ignores all other text between elements" do
|
419
|
-
@loader.load("[array]\n*Value1\nNon-element\n*Value2")['array'].should == ['Value1', 'Value2']
|
420
|
-
end
|
421
|
-
it "ignores key:value pairs between elements" do
|
422
|
-
@loader.load("[array]\n*Value1\nkey:value\n*Value2")['array'].should == ['Value1', 'Value2']
|
423
|
-
end
|
424
|
-
it "parses key:values normally after an end-array" do
|
425
|
-
@loader.load("[array]\n*Value1\n[]\nkey:value")['key'].should == 'value'
|
426
|
-
end
|
427
|
-
it "multi-line values are allowed" do
|
428
|
-
@loader.load("[array]\n*Value1\nextra\n:end")['array'].first.should == "Value1\nextra"
|
429
|
-
end
|
430
|
-
it "allows escaping of '*' within multi-line values in simple arrays" do
|
431
|
-
@loader.load("[array]\n*Value\n\\* extra\n:end")['array'].first.should == "Value\n* extra"
|
432
|
-
end
|
433
|
-
it "allows escaping of command keys within multi-line values" do
|
434
|
-
@loader.load("[array]\n*Value\n\\:end\n:end")['array'].first.should == "Value\n:end"
|
435
|
-
end
|
436
|
-
it "does not allow escaping of keys within multi-line values" do
|
437
|
-
@loader.load("[array]\n*Value\nkey\\:value\n:end")['array'].first.should == "Value\nkey\\:value"
|
438
|
-
end
|
439
|
-
it "allows escaping key lines with a leading backslash" do
|
440
|
-
@loader.load("[array]\n*Value\n\\key:value\n:end")['array'].first.should == "Value\nkey:value"
|
441
|
-
end
|
442
|
-
it "does not allow escaping of colons not at the beginning of lines" do
|
443
|
-
@loader.load("[array]\n*Value\nword key\\:value\n:end")['array'].first.should == "Value\nword key\\:value"
|
444
|
-
end
|
445
|
-
it "arrays within a multi-line value breaks up the value" do
|
446
|
-
@loader.load("[array]\n* value\n[array]\nmore text\n:end")['array'].first.should == "value"
|
447
|
-
end
|
448
|
-
it "objects within a multi-line value breaks up the value" do
|
449
|
-
@loader.load("[array]\n* value\n{scope}\nmore text\n:end")['array'].first.should == "value"
|
450
|
-
end
|
451
|
-
it "key/values within a multi-line value do not break up the value" do
|
452
|
-
@loader.load("[array]\n* value\nkey: value\nmore text\n:end")['array'].first.should == "value\nkey: value\nmore text"
|
453
|
-
end
|
454
|
-
it "bullets within a multi-line value break up the value" do
|
455
|
-
@loader.load("[array]\n* value\n* value\nmore text\n:end")['array'].first.should == "value"
|
456
|
-
end
|
457
|
-
it "skips within a multi-line value do not break up the value" do
|
458
|
-
@loader.load("[array]\n* value\n:skip\n:endskip\nmore text\n:end")['array'].first.should == "value\nmore text"
|
459
|
-
end
|
460
|
-
it "arrays that are reopened add to existing array" do
|
461
|
-
@loader.load("[array]\n*Value\n[]\n[array]\n*Value")['array'].should == ['Value', 'Value']
|
462
|
-
end
|
463
|
-
it "simple arrays that are reopened remain simple" do
|
464
|
-
@loader.load("[array]\n*Value\n[]\n[array]\nkey:value")['array'].should == ['Value']
|
465
|
-
end
|
466
|
-
it "simple arrays overwrite existing keys" do
|
467
|
-
@loader.load("a.b:complex value\n[a.b]\n*simple value")['a']['b'][0].should == 'simple value'
|
468
|
-
end
|
4
|
+
Dir.glob(File.expand_path('../../../archieml.org/test/1.0/*.aml', __FILE__)).each do |f|
|
5
|
+
data = File.read(f)
|
6
|
+
slug, idx = File.basename(f).split('.')
|
469
7
|
|
470
|
-
|
8
|
+
# Parse without inline comments
|
9
|
+
metadata = Archieml::Loader.new.load(data, comments: false)
|
10
|
+
test = metadata['test']
|
11
|
+
result = JSON.parse(metadata['result'])
|
471
12
|
|
472
|
-
|
13
|
+
aml = Archieml::Loader.new.load(data)
|
14
|
+
aml.delete('test')
|
15
|
+
aml.delete('result')
|
473
16
|
|
474
|
-
it "
|
475
|
-
|
476
|
-
end
|
477
|
-
it "array items can have multiple keys" do
|
478
|
-
@loader.load("[array]\nkey:value\nsecond:value")['array'].first.keys.should =~ ['key', 'second']
|
479
|
-
end
|
480
|
-
it "when a duplicate key is encountered, a new item in the array is started" do
|
481
|
-
@loader.load("[array]\nkey:value\nsecond:value\nkey:value")['array'].length.should == 2
|
482
|
-
@loader.load("[array]\nkey:first\nkey:second")['array'].last.should == {'key' => 'second'}
|
483
|
-
@loader.load("[array]\nscope.key:first\nscope.key:second")['array'].last.should == {'scope' => {'key' => 'second'}}
|
484
|
-
end
|
485
|
-
it "duplicate keys must match on dot-notation scope" do
|
486
|
-
@loader.load("[array]\nkey:value\nscope.key:value")['array'].length.should == 1
|
487
|
-
end
|
488
|
-
it "duplicate keys must match on dot-notation scope" do
|
489
|
-
@loader.load("[array]\nscope.key:value\nkey:value\notherscope.key:value")['array'].length.should == 1
|
490
|
-
end
|
491
|
-
it "arrays within a multi-line value breaks up the value" do
|
492
|
-
@loader.load("[array]\nkey:value\n[array]\nmore text\n:end")['array'].first['key'].should == "value"
|
493
|
-
end
|
494
|
-
it "objects within a multi-line value breaks up the value" do
|
495
|
-
@loader.load("[array]\nkey:value\n{scope}\nmore text\n:end")['array'].first['key'].should == "value"
|
496
|
-
end
|
497
|
-
it "key/values within a multi-line value break up the value" do
|
498
|
-
@loader.load("[array]\nkey:value\nother: value\nmore text\n:end")['array'].first['key'].should == "value"
|
499
|
-
end
|
500
|
-
it "bullets within a multi-line value do not break up the value" do
|
501
|
-
@loader.load("[array]\nkey:value\n* value\nmore text\n:end")['array'].first['key'].should == "value\n* value\nmore text"
|
502
|
-
end
|
503
|
-
it "skips within a multi-line value do not break up the value" do
|
504
|
-
@loader.load("[array]\nkey:value\n:skip\n:endskip\nmore text\n:end")['array'].first['key'].should == "value\nmore text"
|
505
|
-
end
|
506
|
-
it "arrays that are reopened add to existing array" do
|
507
|
-
@loader.load("[array]\nkey:value\n[]\n[array]\nkey:value")['array'].length.should == 2
|
508
|
-
end
|
509
|
-
it "complex arrays that are reopened remain complex" do
|
510
|
-
@loader.load("[array]\nkey:value\n[]\n[array]\n*Value")['array'].should == [{'key' => 'value'}]
|
511
|
-
end
|
512
|
-
it "complex arrays overwrite existing keys" do
|
513
|
-
@loader.load("a.b:complex value\n[a.b]\nkey:value")['a']['b'][0]['key'].should == 'value'
|
17
|
+
it "#{slug}.#{idx} #{test}" do
|
18
|
+
aml.should == result
|
514
19
|
end
|
515
|
-
|
516
|
-
end
|
517
|
-
|
518
|
-
describe "inline comments" do
|
519
|
-
|
520
|
-
it "ignore comments inside of [single brackets]" do
|
521
|
-
@loader.load("key:value [inline comments] value")['key'].should == "value value"
|
522
|
-
end
|
523
|
-
it "supports multiple inline comments on a single line" do
|
524
|
-
@loader.load("key:value [inline comments] value [inline comments] value")['key'].should == "value value value"
|
525
|
-
end
|
526
|
-
it "supports adjacent comments" do
|
527
|
-
@loader.load("key:value [inline comments] [inline comments] value")['key'].should == "value value"
|
528
|
-
end
|
529
|
-
it "supports no-space adjacent comments" do
|
530
|
-
@loader.load("key:value [inline comments][inline comments] value")['key'].should == "value value"
|
531
|
-
end
|
532
|
-
it "supports comments at beginning of string" do
|
533
|
-
@loader.load("key:[inline comments] value")['key'].should == "value"
|
534
|
-
end
|
535
|
-
it "supports comments at end of string" do
|
536
|
-
@loader.load("key:value [inline comments]")['key'].should == "value"
|
537
|
-
end
|
538
|
-
it "whitespace before a comment that appears at end of line is ignored" do
|
539
|
-
@loader.load("key:value [inline comments] value [inline comments]")['key'].should == "value value"
|
540
|
-
end
|
541
|
-
it "unmatched single brackets are preserved" do
|
542
|
-
@loader.load("key:value ][ value")['key'].should == "value ][ value"
|
543
|
-
end
|
544
|
-
|
545
|
-
it "inline comments are supported on the first of multi-line values" do
|
546
|
-
@loader.load("key:value [inline comments] on\nmultiline\n:end")['key'].should == "value on\nmultiline"
|
547
|
-
end
|
548
|
-
it "inline comments are supported on subsequent lines of multi-line values" do
|
549
|
-
@loader.load("key:value\nmultiline [inline comments]\n:end")['key'].should == "value\nmultiline"
|
550
|
-
end
|
551
|
-
it "whitespace around comments is preserved, except at the beinning and end of a value" do
|
552
|
-
@loader.load("key: [] value [] \n multiline [] \n:end")['key'].should == "value \n multiline"
|
553
|
-
end
|
554
|
-
|
555
|
-
it "inline comments cannot span multiple lines" do
|
556
|
-
@loader.load("key:value [inline\ncomments] value\n:end")['key'].should == "value [inline\ncomments] value"
|
557
|
-
@loader.load("key:value \n[inline\ncomments] value\n:end")['key'].should == "value \n[inline\ncomments] value"
|
558
|
-
end
|
559
|
-
it "text inside [[double brackets]] is included as [single brackets]" do
|
560
|
-
@loader.load("key:value [[brackets]] value")['key'].should == "value [brackets] value"
|
561
|
-
end
|
562
|
-
it "unmatched double brackets are preserved" do
|
563
|
-
@loader.load("key:value ]][[ value")['key'].should == "value ]][[ value"
|
564
|
-
end
|
565
|
-
it "comments work in simple arrays" do
|
566
|
-
@loader.load("[array]\n*Val[comment]ue")['array'].first.should == "Value"
|
567
|
-
end
|
568
|
-
it "double brackets work in simple arrays" do
|
569
|
-
@loader.load("[array]\n*Val[[real]]ue")['array'].first.should == "Val[real]ue"
|
570
|
-
end
|
571
|
-
|
572
20
|
end
|
573
21
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: archieml
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Strickland
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-05-11 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Parse Archie Markup Language documents
|
14
14
|
email:
|
@@ -18,6 +18,7 @@ extensions: []
|
|
18
18
|
extra_rdoc_files: []
|
19
19
|
files:
|
20
20
|
- ".gitignore"
|
21
|
+
- ".gitmodules"
|
21
22
|
- Gemfile
|
22
23
|
- LICENSE
|
23
24
|
- README.md
|