archieml 0.1.1 → 0.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: 20d1f2ce2e413edb046e36127d145f97d467b723
4
- data.tar.gz: 6f84f4940d148c1e5d01e7e73e1b7f71ae514659
3
+ metadata.gz: e1769d7385a90df46fb88685e1be8cef959943c1
4
+ data.tar.gz: c92bea5829cc1729197ee1035f7398256b3205c8
5
5
  SHA512:
6
- metadata.gz: 98ebe6f5d69d8bc03fb564b1f19b7d54fb2b70eb08526ba3d6d1165d8cd7ef2f7f8995d5ce87637b674265f2589c5cec8bfe6a9221ff21754399e14d54a6cebb
7
- data.tar.gz: 94414bb4b8a3fd0d43d516fa130a4c39ec471d0dad7e2f3cb828405fb3344cedc8cba64aafd0fbc43a8906ba05953b95e7284cac89f573ca5159aa6f2182bfbe
6
+ metadata.gz: 045a8a57c8676be985fd6194c708f09eb157b93ea97ef032d3a88bfe0e4d6431f94924c25e66e0e9265bef9fd54ce07a47a3a2bdbc87e94bbee8d395abc81d00
7
+ data.tar.gz: 7bbd0d896a7ae3348814ab36ec6cbe795739ab9543b1971542671d21b629b26b1c15f32b4e7594ff83898c80dec090e3b8bd12e317b92a1023d0748d9742b19d
data/.gitignore CHANGED
@@ -1,3 +1,5 @@
1
1
  Gemfile.lock
2
2
  .ruby-version
3
3
  .ruby-gemset
4
+ Makefile
5
+ archieml-*.gem
@@ -0,0 +1,4 @@
1
+ [submodule "spec/archieml.org"]
2
+ path = spec/archieml.org
3
+ url = https://github.com/newsdev/archieml.org
4
+ branch = gh-pages
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.1.1`.
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).
@@ -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
- @buffer_scope = @buffer_key = nil
13
+ @stack = []
14
+ @stack_scope = nil
15
+
16
+ @buffer_scope = @buffer_key = nil
14
17
  @buffer_string = ''
15
18
 
16
- @is_skipping = false
19
+ @is_skipping = false
17
20
  @done_parsing = false
18
21
 
19
- self.flush_scope!
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)) && (!@array || @array_type != 'simple')
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)) && @array && @array_type != 'complex'
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
- if @array
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 ||= 'simple'
69
+ @stack_scope[:array_type] ||= :simple
73
70
 
74
71
  # Ignore simple array elements inside complex arrays
75
- return if @array_type == 'complex'
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
- @scope = @data
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
- @array = key_scope[key_bits.last] ||= []
120
- @array = key_scope[key_bits.last] = [] if @array.class == String
121
-
122
- if @array.length > 0
123
- @array_type = @array.first.class == String ? 'simple' : 'complex'
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
- value.gsub!(/(?:^\\)?\[[^\[\]\n\r]*\](?!\])/, '') # remove comments
178
- value.gsub!(/\[\[([^\[\]\n\r]*)\]\]/, '[\1]') # [[]] => []
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')
@@ -1,3 +1,3 @@
1
1
  module Archieml
2
- VERSION = '0.1.1'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -1,573 +1,21 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Archieml::Loader do
4
- before(:each) do
5
- @loader = Archieml::Loader.new()
6
- allow(@loader).to receive(:parse_scope).with(any_args).and_call_original
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
- end
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
- describe "complex arrays" do
13
+ aml = Archieml::Loader.new.load(data)
14
+ aml.delete('test')
15
+ aml.delete('result')
473
16
 
474
- it "keys after an [array] are included as items in the array" do
475
- @loader.load("[array]\nkey:value")['array'].first.should == {'key' => 'value' }
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
@@ -1,2 +1,3 @@
1
1
  require 'archieml'
2
+ require 'json'
2
3
  require 'rspec'
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.1.1
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-03-17 00:00:00.000000000 Z
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