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 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