metaheader 1.3beta3 → 2.0.1

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
- SHA1:
3
- metadata.gz: e4da8975afe832f43e402d8055e6d0b5ffd116b8
4
- data.tar.gz: 8370402e66dd19c47c31959a1104492b38b680b2
2
+ SHA256:
3
+ metadata.gz: 551a95591a97a5578a68b27fd13ec3f7e20a23925884b222bdf1d276fdb58910
4
+ data.tar.gz: f1d46558535100ff763f79f8d1a67fbb8965fd0a305f0cc249815fbe0a3fab78
5
5
  SHA512:
6
- metadata.gz: 61ed6799668cbd4fea43f71cca2318dfb2f8ec1b9d39bcd4fd02762c63c118d9792eadc4e33ca542e4bdcdd85dafda35af69238719a73c702fe188655472f638
7
- data.tar.gz: 77fb389959db93f0aa8ceeba8ca188210e18cca144c5bbf8712dd4067a253a86f14e5728de607a11c54a9af6c8873adeef0b935b80dac1b9c81e0b9b3d16840f
6
+ metadata.gz: 134d937cd06dfa13fd548cee84fa69b7e2368a7e603228cfcc56e54a96b2e321173a026d3df1abfcda35e223aa464f426d00fe2b4cbdd55c6478d5073e0f8f9c
7
+ data.tar.gz: c28c1672dc9feb00d073f199ff53373852915cd75b78603437a67c2c10bbdca290638d5621aca0dbdcaf0d0a281ac97980381bb472249d8afb31b447f1da4e18
data/Gemfile CHANGED
@@ -1,5 +1,3 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'pry'
4
-
5
3
  gemspec
data/README.md CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/metaheader.svg)](http://badge.fury.io/rb/metaheader)
4
4
  [![Build Status](https://travis-ci.org/cfillion/metaheader.svg?branch=master)](https://travis-ci.org/cfillion/metaheader)
5
- [![Coverage Status](https://coveralls.io/repos/cfillion/metaheader/badge.svg?branch=master&service=github)](https://coveralls.io/github/cfillion/metaheader?branch=master)
6
5
 
7
6
  ## Syntax
8
7
 
@@ -20,7 +19,8 @@
20
19
  @key false
21
20
  ```
22
21
 
23
- Any kind of comment syntax or prefix can be used:
22
+ Any kind of comment syntax or prefix can be used (every line of a multiline
23
+ value must begin with the same decoration characters):
24
24
 
25
25
  ```cpp
26
26
  /*
@@ -28,13 +28,14 @@ Any kind of comment syntax or prefix can be used:
28
28
  */
29
29
  ```
30
30
 
31
- An alternative syntax is also supported:
31
+ An alternative syntax that allows spaces in the key name is also supported:
32
32
 
33
33
  ```
34
- Key Name: Value
34
+ Key: Value
35
35
  ```
36
36
 
37
- Parsing stops at the first empty line (ignoring white space).
37
+ Parsing stops at the first empty line outside of a multiline value
38
+ (ignoring white space).
38
39
 
39
40
  ## Usage
40
41
 
@@ -42,14 +43,11 @@ Parsing stops at the first empty line (ignoring white space).
42
43
  require 'metaheader'
43
44
 
44
45
  input = '@key value'
45
- mh = MetaHeader.new input
46
+ mh = MetaHeader.parse input
46
47
 
47
48
  # alternatively:
48
49
  # mh = MetaHeader.from_file path
49
50
 
50
- # mark unknown keys as invalid
51
- # mh.strict = true
52
-
53
51
  # set @key as mandatory
54
52
  errors = mh.validate key: MetaHeader::REQUIRED
55
53
 
@@ -66,13 +64,12 @@ value = mh[:key]
66
64
 
67
65
  ## Documentation
68
66
 
69
- MetaHeader's documentation is hosted at
70
- [http://rubydoc.info/gems/metaheader/MetaHeader](http://rubydoc.info/gems/metaheader/MetaHeader).
67
+ MetaHeader's documentation is hosted at <https://rubydoc.info/gems/metaheader/MetaHeader>.
71
68
 
72
69
  ## Contributing
73
70
 
74
71
  1. [Fork this repository](https://github.com/cfillion/metaheader/fork)
75
- 2. Create your feature branch (`git checkout -b my-new-feature`)
72
+ 2. Create your feature branch (`git switch -c my-new-feature`)
76
73
  3. Commit your changes (`git commit -am 'Add some feature'`)
77
74
  4. Push to the branch (`git push -u origin my-new-feature`)
78
75
  5. Create a new Pull Request
@@ -1,84 +1,69 @@
1
1
  require 'metaheader/version'
2
2
 
3
- class MetaHeader
4
- # @abstract Subclass and override {#parse} to implement a custom parser.
5
- class Parser
6
- class << self
7
- # @api private
8
- def inherited(k)
9
- @parsers ||= []
10
- @parsers << k
11
- end
12
-
13
- # @api private
14
- def each(&b)
15
- @parsers&.each(&b)
16
- end
17
- end
3
+ require 'stringio'
18
4
 
19
- # @return [MetaHeader] the current instance
20
- def header
21
- @mh
22
- end
5
+ # Parser for metadata header in plain-text files
6
+ # @example
7
+ # mh = MetaHeader.parse '@hello world'
8
+ # puts mh[:hello]
9
+ class MetaHeader
10
+ # Allow a tag to be omitted when validating in strict mode.
11
+ OPTIONAL = Object.new.freeze
23
12
 
24
- # @param raw_input [IO]
25
- # @return [void]
26
- def parse(raw_input)
27
- raise NotImplementedError
28
- end
29
- end
13
+ # Ensure a tag exists during validation.
14
+ REQUIRED = Object.new.freeze
30
15
 
16
+ # The tag cannot hold a value beside true or false during validation.
31
17
  BOOLEAN = Object.new.freeze
32
- OPTIONAL = Object.new.freeze
33
- REQUIRED = Object.new.freeze
34
- SINGLELINE = Object.new.freeze
18
+
19
+ # The tag must have a string value (non-boolean tag) during validation.
35
20
  VALUE = Object.new.freeze
36
21
 
37
- # Whether to fail validation if unknown tags are encoutered.
38
- # @see #validate
39
- # @return [Boolean]
40
- attr_accessor :strict
22
+ # Don't allow multiline values during validation.
23
+ SINGLELINE = Object.new.freeze
41
24
 
42
25
  # Create a new instance from the contents of a file.
43
26
  # @param path [String] path to the file to be read
44
27
  # @return [MetaHeader]
45
28
  def self.from_file(path)
46
- File.open(path) {|file| self.new file }
29
+ File.open(path) {|file| self.parse file }
47
30
  end
48
31
 
49
- # Construct a new MetaHeader object or return the object untouched
50
- # @param input [String, MetaHeader]
32
+ # Construct a MetaHeader object and parse every tags found in the input up to
33
+ # the first newline.
34
+ # @param input [String, IO, StringIO]
51
35
  # @return [MetaHeader]
52
36
  def self.parse(input)
53
- if input.is_a? self
54
- input
55
- else
56
- self.new input
57
- end
37
+ mh = MetaHeader.new
38
+ mh.parse input
39
+ mh
58
40
  end
59
41
 
60
- # Parse every tags found in input up to the first newline.
61
- # @param input [String, IO]
62
- def initialize(input)
63
- @strict = false
42
+ # Construct a blank MetaHeader object
43
+ def initialize
64
44
  @data = {}
45
+ end
65
46
 
66
- @last_tag = nil
67
- @empty_lines = 0
68
-
69
- unless input.is_a? IO
47
+ # Parse every tags found in the input up to the first newline.
48
+ # @param input [String, IO, StringIO]
49
+ # @return [Integer] Character position of the first content line in the input
50
+ # data following the header.
51
+ def parse(input)
52
+ if input.is_a? String
70
53
  input = StringIO.new input.encode universal_newline: true
71
54
  end
72
55
 
73
- input.each_line {|line| break unless parse line }
74
-
75
- Parser.each {|klass|
76
- input.rewind
56
+ @last_tag = nil
57
+ @empty_lines = 0
77
58
 
78
- parser = klass.new
79
- parser.instance_variable_set :@mh, self
80
- parser.parse input
81
- }
59
+ content_offset = 0
60
+ input.each_line do |line|
61
+ full_line_size = line.size # parse_line can trim the line
62
+ continue = parse_line line
63
+ content_offset += full_line_size if continue || line.empty?
64
+ break unless continue
65
+ end
66
+ content_offset
82
67
  end
83
68
 
84
69
  # Returns the value of a tag by its name, or nil if not found.
@@ -95,7 +80,7 @@ class MetaHeader
95
80
 
96
81
  # Replaces the value of a tag.
97
82
  # @param value the new value
98
- # @return value
83
+ # @return [Object] value
99
84
  def []=(key, value)
100
85
  raise ArgumentError, 'value cannot be nil' if value.nil?
101
86
 
@@ -104,7 +89,7 @@ class MetaHeader
104
89
  end
105
90
 
106
91
  # Returns how many tags were found in the input.
107
- # @return [Fixnum]
92
+ # @return [Integer]
108
93
  def size
109
94
  @data.size
110
95
  end
@@ -129,7 +114,7 @@ class MetaHeader
129
114
  end
130
115
 
131
116
  # Make a hash from the parsed data
132
- # @return [Hash]
117
+ # @return [Hash<Symbol, Object>]
133
118
  def to_h
134
119
  Hash[@data.map {|name, tag| [name, tag.value] }]
135
120
  end
@@ -141,22 +126,25 @@ class MetaHeader
141
126
  end
142
127
 
143
128
  # Validates parsed data according to a custom set of rules.
129
+ # A rule can be one of the predefined constants, a regex, a proc or a
130
+ # method (returing nil if the tag is valid or an error string otherwise).
144
131
  # @example
145
132
  # mh = MetaHeader.new "@hello world\n@chunky bacon"
146
133
  # mh.validate \
147
134
  # hello: [MetaHeader::REQUIRED, MetaHeader::SINGLELINE, /\d/],
148
135
  # chunky: proc {|value| 'not bacon' unless value == 'bacon' }
149
- # @param rules [Hash] tag_name => rule or array_of_rules
150
- # @return [Array, nil] error list or nil
151
- # @see BOOLEAN
136
+ # @param rules [Hash] :tag_name => rule or [rule1, rule2, ...]
137
+ # @param strict [Boolean] Whether to report unknown tags as errors
138
+ # @return [Array<String>] List of error messasges
152
139
  # @see OPTIONAL
153
140
  # @see REQUIRED
154
- # @see SINGLELINE
141
+ # @see BOOLEAN
155
142
  # @see VALUE
156
- def validate(rules)
157
- errors = Array.new
143
+ # @see SINGLELINE
144
+ def validate(rules, strict = false)
145
+ errors = []
158
146
 
159
- if @strict
147
+ if strict
160
148
  @data.each {|key, tag|
161
149
  errors << "unknown tag '%s'" % tag.name unless rules.has_key? key
162
150
  }
@@ -168,27 +156,22 @@ class MetaHeader
168
156
  end
169
157
  }
170
158
 
171
- errors unless errors.empty?
159
+ errors
172
160
  end
173
161
 
174
162
  # Rename one or more tags.
175
- # @param old [Symbol, Hash]
176
- # @param new [Symbol]
163
+ # @param old [Symbol, Array<Symbol>, Hash<Symbol, Symbol>]
164
+ # @param new [Symbol] Ignored if old is a Hash
177
165
  # @example
178
166
  # mh.alias :old, :new
179
- # mh.alias :old1, :old2, :new
180
167
  # mh.alias [:old1, :old2], :new
181
168
  # mh.alias old1: :new1, old2: :new2
182
- def alias(*args)
183
- raise ArgumentError, 'wrong number of arguments' unless args.size.between? 1, 2
184
-
185
- tags, new = args
186
-
187
- if args.size == 1
188
- tags.each {|k, v| self.alias k, v }
169
+ def alias(old, new = nil)
170
+ if old.is_a? Hash
171
+ old.each {|k, v| self.alias k, v }
189
172
  else
190
- Array(tags).each {|old|
191
- @data[new] = delete old if has? old
173
+ Array(old).each {|tag|
174
+ @data[new] = delete tag if has? tag
192
175
  }
193
176
  end
194
177
  end
@@ -202,8 +185,9 @@ private
202
185
  (?:\s*(?<value>[^\n]+))?
203
186
  \Z/x.freeze
204
187
 
205
- def parse(line)
188
+ def parse_line(line)
206
189
  line.chomp!
190
+ line.encode! Encoding::UTF_8, invalid: :replace
207
191
 
208
192
  # multiline value must have the same line prefix as the key
209
193
  if @last_tag && line.start_with?(@last_prefix.rstrip)
@@ -227,6 +211,7 @@ private
227
211
 
228
212
  @last_tag = Tag.new match[:key].freeze, value
229
213
  @line_breaks = 0
214
+ @block_indent = nil
230
215
 
231
216
  # disable implicit values with the alternate syntax
232
217
  register @last_tag unless match[:alt] && match[:value].nil?
@@ -236,10 +221,12 @@ private
236
221
  end
237
222
 
238
223
  def register(tag)
224
+ return if has? tag
239
225
  key = tag.name.downcase.gsub(/[^\w]/, '_').to_sym
240
226
  @data[key] = tag
241
227
  end
242
228
 
229
+ # handle multiline tags
243
230
  def append(line)
244
231
  prefix = line.rstrip
245
232
  if prefix == @last_prefix.rstrip
@@ -248,9 +235,19 @@ private
248
235
  return true # add the line break later
249
236
  elsif line.start_with? @last_prefix
250
237
  mline = line[@last_prefix.size..-1]
251
- stripped = mline.lstrip
252
- indent_level = mline.index stripped
253
- return if indent_level < 1
238
+
239
+ if @block_indent
240
+ if mline.start_with? @block_indent
241
+ stripped = mline[@block_indent.size..-1]
242
+ else
243
+ return
244
+ end
245
+ else
246
+ stripped = mline.lstrip
247
+ indent_level = mline.index stripped
248
+ return if indent_level < 1
249
+ @block_indent = mline[0, indent_level]
250
+ end
254
251
  else
255
252
  return
256
253
  end
@@ -1,4 +1,4 @@
1
1
  class MetaHeader
2
2
  # MetaHeader's version
3
- VERSION = '1.3beta3'.freeze
3
+ VERSION = '2.0.1'.freeze
4
4
  end
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
8
8
  spec.name = "metaheader"
9
9
  spec.version = MetaHeader::VERSION
10
10
  spec.authors = ["cfillion"]
11
- spec.email = ["metaheader@cfillion.tk"]
11
+ spec.email = ["metaheader@cfillion.ca"]
12
12
  spec.summary = %q{Parser for metadata headers in plain-text files}
13
13
  spec.homepage = "https://github.com/cfillion/metaheader"
14
14
  spec.license = "LGPL-3.0+"
@@ -20,9 +20,8 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.required_ruby_version = '>= 2.3'
22
22
 
23
- spec.add_development_dependency 'bundler', '~> 1.10'
24
- spec.add_development_dependency 'coveralls', '~> 0.8'
25
- spec.add_development_dependency 'minitest', '~> 5.8'
26
- spec.add_development_dependency 'rake', '~> 11.0'
27
- spec.add_development_dependency 'simplecov', '~> 0.11'
23
+ spec.add_development_dependency 'bundler', '~> 2.0'
24
+ spec.add_development_dependency 'minitest', '~> 5.10'
25
+ spec.add_development_dependency 'rake', '~> 13.0'
26
+ spec.add_development_dependency 'simplecov', '~> 0.13'
28
27
  end
@@ -1,11 +1,7 @@
1
- require 'coveralls'
2
1
  require 'simplecov'
3
2
 
4
- Coveralls::Output.silent = true
5
-
6
3
  SimpleCov.formatters = [
7
4
  SimpleCov::Formatter::HTMLFormatter,
8
- Coveralls::SimpleCov::Formatter,
9
5
  ]
10
6
 
11
7
  SimpleCov.start {
@@ -0,0 +1,193 @@
1
+ require File.expand_path '../helper', __FILE__
2
+
3
+ class TestMultiline < MiniTest::Test
4
+ def test_multiline
5
+ mh = MetaHeader.parse <<-IN
6
+ @test Lorem
7
+ Ipsum
8
+ IN
9
+
10
+ assert_equal "Lorem\nIpsum", mh[:test]
11
+ assert_equal 1, mh.size
12
+ end
13
+
14
+ def test_variant
15
+ mh = MetaHeader.parse <<-IN
16
+ @test
17
+ Lorem
18
+ Ipsum
19
+ IN
20
+
21
+ assert_equal "Lorem\nIpsum", mh[:test]
22
+ end
23
+
24
+ def test_trailing_space
25
+ mh = MetaHeader.parse <<-IN
26
+ @hello\x20
27
+ test\x20
28
+ @world\x20\x20
29
+ test\x20
30
+ IN
31
+
32
+ assert_equal 'test ', mh[:hello]
33
+ assert_equal 'test ', mh[:world]
34
+ end
35
+
36
+ def test_prefix
37
+ mh = MetaHeader.parse <<-IN
38
+ -- @test Lorem
39
+ -- Ipsum
40
+ IN
41
+
42
+ assert_equal "Lorem\nIpsum", mh[:test]
43
+ assert_equal 1, mh.size
44
+ end
45
+
46
+ def test_no_indent
47
+ mh = MetaHeader.parse <<-IN
48
+ @test Lorem
49
+ Ipsum
50
+ Test
51
+ IN
52
+
53
+ assert_equal 1, mh.size
54
+ assert_equal "Lorem", mh[:test]
55
+ end
56
+
57
+ def test_lose_indent
58
+ mh = MetaHeader.parse <<-IN
59
+ @test
60
+ Hello
61
+
62
+ World
63
+ @chunky bacon
64
+
65
+ @foo
66
+ IN
67
+
68
+ assert_equal "Hello\n\nWorld", mh[:test]
69
+ assert_equal 'bacon', mh[:chunky]
70
+ assert_nil mh[:foo]
71
+ end
72
+
73
+ def test_fewer_indent
74
+ mh = MetaHeader.parse <<-IN
75
+ @test Lorem
76
+ Ipsum
77
+ Hello
78
+ World
79
+ IN
80
+
81
+ assert_equal 1, mh.size
82
+ assert_equal "Lorem\nIpsum", mh[:test]
83
+ end
84
+
85
+ def test_extra_indent
86
+ mh = MetaHeader.parse <<-IN
87
+ @test Lorem
88
+ Ipsum
89
+ Hello
90
+ World
91
+ IN
92
+
93
+ assert_equal 1, mh.size
94
+ assert_equal "Lorem\nIpsum\n Hello\nWorld", mh[:test]
95
+ end
96
+
97
+ def test_do_not_reuse_indent
98
+ mh = MetaHeader.parse <<-IN
99
+ @a
100
+ Lorem
101
+ Ipsum
102
+ @b
103
+ \tHello
104
+ \tWorld
105
+ IN
106
+
107
+ assert_equal 2, mh.size
108
+ assert_equal "Lorem\nIpsum", mh[:a]
109
+ assert_equal "Hello\nWorld", mh[:b]
110
+ end
111
+
112
+ def test_sub_alternate_syntax
113
+ mh = MetaHeader.parse <<-IN
114
+ @test Lorem
115
+ Ipsum:
116
+ Dolor: sit amet
117
+ IN
118
+
119
+ assert_equal "Lorem\nIpsum:\nDolor: sit amet", mh[:test]
120
+ assert_equal 1, mh.size
121
+ end
122
+
123
+ def test_explicit_boolean
124
+ mh = MetaHeader.parse <<-IN
125
+ @test true
126
+ test
127
+ IN
128
+
129
+ assert_equal "true\ntest", mh[:test]
130
+ end
131
+
132
+ def test_empty_line_prefix
133
+ mh = MetaHeader.parse <<-IN
134
+ --@test
135
+ -- Hello
136
+ --
137
+ -- World
138
+ --
139
+ --@chunky
140
+ -- bacon
141
+ IN
142
+
143
+ assert_equal "Hello\n\nWorld", mh[:test]
144
+ assert_equal 'bacon', mh[:chunky]
145
+ end
146
+
147
+ def test_empty_line_prefix_with_space
148
+ mh = MetaHeader.parse <<-IN
149
+ -- @test
150
+ -- Hello
151
+ --
152
+ -- World
153
+ IN
154
+
155
+ assert_equal "Hello\n\nWorld", mh[:test]
156
+ end
157
+
158
+ def test_empty_line
159
+ mh = MetaHeader.parse <<-IN
160
+ @test
161
+ Hello
162
+
163
+ World
164
+ @chunky bacon
165
+
166
+ @foo
167
+ IN
168
+
169
+ assert_equal "Hello\n\nWorld", mh[:test]
170
+ assert_equal 'bacon', mh[:chunky]
171
+ assert_nil mh[:foo]
172
+ end
173
+
174
+ def test_break_at_empty_line
175
+ mh = MetaHeader.parse <<-IN
176
+ -- @hello world
177
+
178
+ @chunky bacon
179
+ IN
180
+
181
+ assert_equal 'world', mh[:hello]
182
+ assert_nil mh[:chunky]
183
+ end
184
+
185
+ def test_alternate_syntax
186
+ mh = MetaHeader.parse <<-IN
187
+ -- Hello:
188
+ -- World
189
+ IN
190
+
191
+ assert_equal 'World', mh[:hello]
192
+ end
193
+ end
@@ -1,47 +1,15 @@
1
1
  require File.expand_path '../helper', __FILE__
2
2
 
3
- class CustomParser < MetaHeader::Parser
4
- def self.reset
5
- @@called = false
6
- end
7
-
8
- def self.called?
9
- @@called
10
- end
11
-
12
- def self.input
13
- @@input
14
- end
15
-
16
- def self.instance
17
- @@instance
18
- end
19
-
20
- def parse(input)
21
- return unless header[:run_custom]
22
-
23
- header[:hello] = header[:hello].to_s * 2
24
-
25
- @@input = input.read
26
- @@instance = header
27
- @@called = true
28
- end
29
- end
30
-
31
3
  class TestParser < MiniTest::Test
32
- def setup
33
- CustomParser.reset
34
- end
35
-
36
4
  def test_basic_parser
37
- mh = MetaHeader.new '@hello world'
5
+ mh = MetaHeader.parse '@hello world'
38
6
 
39
7
  assert_equal 'world', mh[:hello]
40
8
  assert_equal 1, mh.size
41
9
  end
42
10
 
43
11
  def test_set_value
44
- mh = MetaHeader.new String.new
12
+ mh = MetaHeader.parse String.new
45
13
 
46
14
  assert_empty mh
47
15
  mh[:hello] = 'world'
@@ -57,23 +25,23 @@ class TestParser < MiniTest::Test
57
25
  end
58
26
 
59
27
  def test_implicit_boolean
60
- mh = MetaHeader.new "@hello"
28
+ mh = MetaHeader.parse "@hello"
61
29
  assert_equal true, mh[:hello]
62
30
  end
63
31
 
64
32
  def test_explicit_boolean
65
- mh = MetaHeader.new "@foo true\n@bar false"
33
+ mh = MetaHeader.parse "@foo true\n@bar false"
66
34
  assert_equal true, mh[:foo]
67
35
  assert_equal false, mh[:bar]
68
36
  end
69
37
 
70
38
  def test_ignore_prefix
71
- mh = MetaHeader.new '-- @chunky bacon'
39
+ mh = MetaHeader.parse '-- @chunky bacon'
72
40
  assert_equal 'bacon', mh[:chunky]
73
41
  end
74
42
 
75
43
  def test_two_tags
76
- mh = MetaHeader.new <<-IN
44
+ mh = MetaHeader.parse <<-IN
77
45
  -- @chunky bacon
78
46
  -- @hello world
79
47
  IN
@@ -84,7 +52,7 @@ class TestParser < MiniTest::Test
84
52
  end
85
53
 
86
54
  def test_break_at_empty_line
87
- mh = MetaHeader.new <<-IN
55
+ mh = MetaHeader.parse <<-IN
88
56
  @hello world
89
57
  \x20
90
58
  @chunky bacon
@@ -95,7 +63,7 @@ class TestParser < MiniTest::Test
95
63
  end
96
64
 
97
65
  def test_ignore_c_comment_tokens
98
- mh = MetaHeader.new <<-IN
66
+ mh = MetaHeader.parse <<-IN
99
67
  /*
100
68
  -- @hello world
101
69
  */
@@ -110,12 +78,12 @@ class TestParser < MiniTest::Test
110
78
  end
111
79
 
112
80
  def test_trailing_whitespace
113
- mh = MetaHeader.new '@hello world '
81
+ mh = MetaHeader.parse '@hello world '
114
82
  assert_equal 'world', mh[:hello]
115
83
  end
116
84
 
117
85
  def test_empty_prefixed_line
118
- mh = MetaHeader.new <<-IN
86
+ mh = MetaHeader.parse <<-IN
119
87
  -- @first
120
88
  --
121
89
  -- @second
@@ -124,272 +92,145 @@ class TestParser < MiniTest::Test
124
92
  refute_nil mh[:second]
125
93
  end
126
94
 
127
- def test_multiline
128
- mh = MetaHeader.new <<-IN
129
- @test Lorem
130
- Ipsum
131
- IN
132
-
133
- assert_equal "Lorem\nIpsum", mh[:test]
134
- assert_equal 1, mh.size
135
- end
136
-
137
- def test_multiline_variant
138
- mh = MetaHeader.new <<-IN
139
- @test
140
- Lorem
141
- Ipsum
142
- IN
143
-
144
- assert_equal "Lorem\nIpsum", mh[:test]
145
- end
146
-
147
- def test_multiline_trailing_space
148
- mh = MetaHeader.new <<-IN
149
- @hello\x20
150
- test\x20
151
- @world\x20\x20
152
- test\x20
153
- IN
154
-
155
- assert_equal 'test ', mh[:hello]
156
- assert_equal 'test ', mh[:world]
157
- end
158
-
159
- def test_multiline_prefix
160
- mh = MetaHeader.new <<-IN
161
- -- @test Lorem
162
- -- Ipsum
163
- IN
164
-
165
- assert_equal "Lorem\nIpsum", mh[:test]
166
- assert_equal 1, mh.size
167
- end
168
-
169
- def test_multiline_wrong_indent
170
- mh = MetaHeader.new <<-IN
171
- @test Lorem
172
- Ipsum
173
- Test
174
- IN
175
-
176
- assert_equal 1, mh.size
177
- assert_equal "Lorem", mh[:test]
178
- end
179
-
180
- def test_multiline_sub_alternate_syntax
181
- mh = MetaHeader.new <<-IN
182
- @test Lorem
183
- Ipsum:
184
- Dolor: sit amet
185
- IN
95
+ def test_from_file
96
+ path = File.expand_path '../input/basic_tag', __FILE__
97
+ mh = MetaHeader.from_file path
186
98
 
187
- assert_equal "Lorem\nIpsum:\nDolor: sit amet", mh[:test]
99
+ assert_equal 'Hello World', mh[:test]
188
100
  assert_equal 1, mh.size
189
101
  end
190
102
 
191
- def test_multiline_explicit_boolean
192
- mh = MetaHeader.new <<-IN
193
- @test true
194
- test
195
- IN
196
-
197
- assert_equal "true\ntest", mh[:test]
198
- end
199
-
200
- def test_multiline_empty_line_prefix
201
- mh = MetaHeader.new <<-IN
202
- --@test
203
- -- Hello
204
- --
205
- -- World
206
- --
207
- --@chunky
208
- -- bacon
209
- IN
210
-
211
- assert_equal "Hello\n\nWorld", mh[:test]
212
- assert_equal 'bacon', mh[:chunky]
213
- end
214
-
215
- def test_multiline_empty_line_space_prefix
216
- mh = MetaHeader.new <<-IN
217
- -- @test
218
- -- Hello
219
- --
220
- -- World
221
- IN
222
-
223
- assert_equal "Hello\n\nWorld", mh[:test]
224
- end
225
-
226
- def test_multiline_empty_line
227
- mh = MetaHeader.new <<-IN
228
- @test
229
- Hello
230
-
231
- World
232
- @chunky bacon
233
-
234
- @foo
235
- IN
236
-
237
- assert_equal "Hello\n\nWorld", mh[:test]
238
- assert_equal 'bacon', mh[:chunky]
239
- assert_nil mh[:foo]
240
- end
241
-
242
- def test_multiline_break_at_empty_line
243
- mh = MetaHeader.new <<-IN
244
- -- @hello world
245
-
246
- @chunky bacon
247
- IN
248
-
249
- assert_equal 'world', mh[:hello]
250
- assert_nil mh[:chunky]
251
- end
252
-
253
- def test_multiline_alternate_syntax
254
- mh = MetaHeader.new <<-IN
255
- -- Hello:
256
- -- World
257
- IN
258
-
259
- assert_equal 'World', mh[:hello]
260
- end
261
-
262
- def test_read_file
103
+ def test_read_file_stream
263
104
  path = File.expand_path '../input/basic_tag', __FILE__
264
- mh = MetaHeader.from_file path
105
+ mh = MetaHeader.parse File.open(path)
265
106
 
266
107
  assert_equal 'Hello World', mh[:test]
267
108
  assert_equal 1, mh.size
268
109
  end
269
110
 
111
+ def test_read_string_stream
112
+ stream = StringIO.new '@hello world'
113
+ MetaHeader.parse stream
114
+ end
115
+
270
116
  def test_to_hash
271
- mh = MetaHeader.new '@key value'
117
+ mh = MetaHeader.parse '@key value'
272
118
  assert_equal Hash[key: 'value'], mh.to_h
273
119
  end
274
120
 
275
121
  def test_alternate_syntax
276
- mh = MetaHeader.new 'Key Test: value'
122
+ mh = MetaHeader.parse 'Key Test: value'
277
123
  assert_equal Hash[key_test: 'value'], mh.to_h
278
124
  end
279
125
 
280
126
  def test_alternate_syntax_prefix
281
- mh = MetaHeader.new '-- Key Test: Value'
127
+ mh = MetaHeader.parse '-- Key Test: Value'
282
128
  assert_equal Hash[key_test: 'Value'], mh.to_h
283
129
  end
284
130
 
285
- def test_windows_newlines
286
- mh = MetaHeader.new "key: value\r\n@run_custom"
131
+ def test_crlf_newlines
132
+ mh = MetaHeader.parse "key: value\r\n@run_custom"
287
133
  assert_equal 'value', mh[:key]
288
- assert_equal "key: value\n@run_custom", CustomParser.input
134
+ assert_equal true, mh[:run_custom]
289
135
  end
290
136
 
291
137
  def test_alternate_syntax_trailing_space
292
- mh = MetaHeader.new ' Key Test : Value'
138
+ mh = MetaHeader.parse ' Key Test : Value'
293
139
  assert_equal Hash[key_test: 'Value'], mh.to_h
294
140
  end
295
141
 
296
142
  def test_alternate_syntax_compact
297
- mh = MetaHeader.new 'Key Test:Value'
143
+ mh = MetaHeader.parse 'Key Test:Value'
298
144
  assert_equal Hash[key_test: 'Value'], mh.to_h
299
145
  end
300
146
 
301
147
  def test_alternate_syntax_no_value
302
- mh = MetaHeader.new 'Key Test:'
148
+ mh = MetaHeader.parse 'Key Test:'
303
149
  assert_equal Hash.new, mh.to_h
304
150
  end
305
151
 
306
152
  def test_inspect
307
- mh = MetaHeader.new '@hello world'
153
+ mh = MetaHeader.parse '@hello world'
308
154
 
309
155
  hash = {hello: 'world'}
310
156
  assert_equal "#<MetaHeader #{hash.inspect}>", mh.inspect
311
157
  end
312
158
 
313
- def test_default_parser_implementation
314
- assert_raises NotImplementedError do
315
- MetaHeader::Parser.new.parse String.new
316
- end
317
- end
318
-
319
- def test_transform_from_text
320
- input = "@run_custom\nHello\n\nWorld".freeze
321
-
322
- mh = MetaHeader.new input
323
-
324
- assert CustomParser.called?
325
- assert_equal input, CustomParser.input
326
- assert_same mh, CustomParser.instance
327
- end
328
-
329
- def test_transform_from_file
330
- path = File.expand_path '../input/custom_parser', __FILE__
331
-
332
- mh = MetaHeader.from_file path
333
- assert_equal 'worldworld', mh[:hello]
334
-
335
- assert CustomParser.called?
336
- assert_equal File.read(path), CustomParser.input
337
- assert_same mh, CustomParser.instance
338
- end
339
-
340
159
  def test_has_tag
341
- mh = MetaHeader.new '@hello'
160
+ mh = MetaHeader.parse '@hello'
342
161
  assert_equal true, mh.has?(:hello)
343
162
  assert_equal false, mh.has?(:world)
344
163
  end
345
164
 
346
165
  def test_default_value
347
- mh = MetaHeader.new String.new
166
+ mh = MetaHeader.parse String.new
348
167
  assert_equal 'world', mh[:hello, 'world']
349
168
  end
350
169
 
351
170
  def test_delete
352
- mh = MetaHeader.new '@hello world'
171
+ mh = MetaHeader.new
172
+ mh[:hello] = 'world'
353
173
  assert mh.has?(:hello)
354
174
  mh.delete :hello
355
175
  refute mh.has?(:hello)
356
176
  end
357
177
 
358
- def test_construct_from_instance
359
- mh = MetaHeader.new '@hello world'
360
- assert_same mh, MetaHeader.parse(mh)
361
- assert_equal mh.to_h, MetaHeader.parse('@hello world').to_h
362
- end
363
-
364
178
  def test_alias
365
- mh = MetaHeader.new '@a 1'
179
+ mh = MetaHeader.new
180
+ mh[:a] = '1'
366
181
  mh.alias :a, :b
367
182
  refute mh.has?(:a)
368
183
  assert_equal '1', mh[:b]
369
-
370
- mh[:d] = '2'
371
- mh.alias :c, :d
372
- refute mh.has?(:c)
373
- assert_equal '2', mh[:d]
374
184
  end
375
185
 
376
186
  def test_alias_hash
377
- mh = MetaHeader.new "@a 1\n@b 2"
187
+ mh = MetaHeader.new
188
+ mh[:a] = '1'
189
+ mh[:b] = '2'
378
190
  mh.alias a: :c, b: :d
379
191
  assert_equal '1', mh[:c]
380
192
  assert_equal '2', mh[:d]
381
193
  end
382
194
 
383
195
  def test_alias_array
384
- mh = MetaHeader.new "@a 1\n@b 2"
196
+ mh = MetaHeader.new
197
+ mh[:a] = '1'
198
+ mh[:b] = '2'
385
199
  mh.alias [:a, :b, :c], :d
386
200
  assert [:a, :b, :c].none? {|t| mh.has? t }
387
201
  assert_equal '2', mh[:d]
388
202
  end
389
203
 
390
- def test_alias_invalid_args
391
- mh = MetaHeader.new "@a 1\n@b 2"
392
- assert_raises(ArgumentError) { mh.alias }
393
- assert_raises(ArgumentError) { mh.alias 1, 2, 3 }
204
+ def test_utf16_bom
205
+ mh = MetaHeader.parse "\xff\xfe@a b\n"
206
+ assert_equal 'b', mh[:a]
207
+ end
208
+
209
+ def test_content_offset
210
+ mh = MetaHeader.new
211
+ input = "@hello\n@world s\n\nafter\n"
212
+ content_offset = mh.parse input
213
+ assert_equal input.index('after'), content_offset
214
+ end
215
+
216
+ def test_content_offset_decorated
217
+ mh = MetaHeader.new
218
+ input = "# @hello\n# @world\n\nafter\n"
219
+ content_offset = mh.parse input
220
+ assert_equal input.index('after'), content_offset
221
+ end
222
+
223
+ def test_content_offset_unicode
224
+ mh = MetaHeader.new
225
+ input = "@lang русский\n\nafter"
226
+ content_offset = mh.parse input
227
+ assert_equal input.index('after'), content_offset
228
+ end
229
+
230
+ def test_content_offset_trailing_space
231
+ mh = MetaHeader.new
232
+ input = "@foo\x20\x20\n bar\n\nafter\n"
233
+ content_offset = mh.parse input
234
+ assert_equal input.index('after'), content_offset
394
235
  end
395
236
  end
@@ -2,42 +2,38 @@ require File.expand_path '../helper', __FILE__
2
2
 
3
3
  class TestValidator < MiniTest::Test
4
4
  def validate(input, rules)
5
- MetaHeader.new(input).validate(rules)
5
+ MetaHeader.parse(input).validate(rules)
6
6
  end
7
7
 
8
8
  def test_unknown_strict
9
- mh = MetaHeader.new "@hello\n@WORLD"
10
- mh.strict = true
11
-
12
- actual = mh.validate Hash.new
13
- assert_equal ["unknown tag 'hello'", "unknown tag 'WORLD'"], actual
9
+ mh = MetaHeader.parse "@hello\n@WORLD"
10
+ errors = mh.validate Hash.new, true
11
+ assert_equal ["unknown tag 'hello'", "unknown tag 'WORLD'"], errors
14
12
  end
15
13
 
16
14
  def test_unknown_tolerant
17
- mh = MetaHeader.new "@hello\n@world"
18
- refute mh.strict
19
-
20
- assert_nil mh.validate(Hash.new)
15
+ mh = MetaHeader.parse "@hello\n@world"
16
+ assert_empty mh.validate(Hash.new, false)
21
17
  end
22
18
 
23
19
  def test_strict_optional
24
- mh = MetaHeader.new "@hello"
25
- mh.strict = true
26
-
27
- actual = mh.validate \
20
+ rules = {
28
21
  hello: MetaHeader::OPTIONAL,
29
- world: MetaHeader::OPTIONAL
22
+ world: MetaHeader::OPTIONAL,
23
+ }
30
24
 
31
- assert_nil actual
25
+ mh = MetaHeader.parse "@hello"
26
+ errors = mh.validate rules, true
27
+ assert_empty errors
32
28
  end
33
29
 
34
30
  def test_required
35
- actual = validate '@foobar', version: MetaHeader::REQUIRED, foobar: []
36
- assert_equal ["missing tag 'version'"], actual
31
+ errors = validate '@foobar', version: MetaHeader::REQUIRED, foobar: []
32
+ assert_equal ["missing tag 'version'"], errors
37
33
  end
38
34
 
39
35
  def test_singleline
40
- mh = MetaHeader.new <<-IN
36
+ mh = MetaHeader.parse <<-IN
41
37
  @hello
42
38
  chunky
43
39
  bacon
@@ -46,50 +42,50 @@ class TestValidator < MiniTest::Test
46
42
  bar
47
43
  IN
48
44
 
49
- actual = mh.validate :hello => MetaHeader::SINGLELINE
50
- assert_equal ["tag 'hello' must be singleline"], actual
45
+ errors = mh.validate :hello => MetaHeader::SINGLELINE
46
+ assert_equal ["tag 'hello' must be singleline"], errors
51
47
  end
52
48
 
53
49
  def test_has_value
54
- mh = MetaHeader.new '@hello'
50
+ mh = MetaHeader.parse '@hello'
55
51
 
56
- actual = mh.validate :hello => [MetaHeader::VALUE]
57
- assert_equal ["missing value for tag 'hello'"], actual
52
+ errors = mh.validate :hello => [MetaHeader::VALUE]
53
+ assert_equal ["missing value for tag 'hello'"], errors
58
54
  end
59
55
 
60
56
  def test_regex
61
- actual = validate '@hello world', :hello => /\d+/
62
- assert_equal ["invalid value for tag 'hello'"], actual
57
+ errors = validate '@hello world', :hello => /\d+/
58
+ assert_equal ["invalid value for tag 'hello'"], errors
63
59
  end
64
60
 
65
61
  def test_regex_no_value
66
- mh = MetaHeader.new '@hello'
62
+ mh = MetaHeader.parse '@hello'
67
63
 
68
- actual = mh.validate :hello => [/./]
69
- assert_equal ["invalid value for tag 'hello'"], actual
64
+ errors = mh.validate :hello => [/./]
65
+ assert_equal ["invalid value for tag 'hello'"], errors
70
66
  end
71
67
 
72
68
  def test_custom_validator
73
- actual = validate '@hello',
69
+ errors = validate '@hello',
74
70
  hello: Proc.new {|value| assert_equal true, value; nil }
75
- assert_nil actual
71
+ assert_empty errors
76
72
 
77
- actual = validate '@hello world',
73
+ errors = validate '@hello world',
78
74
  hello: Proc.new {|value| assert_equal 'world', value; nil }
79
- assert_nil actual
75
+ assert_empty errors
80
76
 
81
- actual = validate '@hello', hello: Proc.new {|value| 'Hello World!' }
82
- assert_equal ["invalid value for tag 'hello': Hello World!"], actual
77
+ errors = validate '@hello', hello: Proc.new {|value| 'Hello World!' }
78
+ assert_equal ["invalid value for tag 'hello': Hello World!"], errors
83
79
  end
84
80
 
85
81
  def test_error_use_original_case
86
- actual = validate 'HeLlO: world', hello: /\d+/
87
- assert_equal ["invalid value for tag 'HeLlO'"], actual
82
+ errors = validate 'HeLlO: world', hello: /\d+/
83
+ assert_equal ["invalid value for tag 'HeLlO'"], errors
88
84
  end
89
85
 
90
86
  def test_single_error_per_tag
91
- actual = validate '@hello', hello: [/\d/, /\d/]
92
- assert_equal ["invalid value for tag 'hello'"], actual
87
+ errors = validate '@hello', hello: [/\d/, /\d/]
88
+ assert_equal ["invalid value for tag 'hello'"], errors
93
89
  end
94
90
 
95
91
  def test_invalid_rule
@@ -103,13 +99,13 @@ class TestValidator < MiniTest::Test
103
99
  end
104
100
 
105
101
  def test_boolean
106
- actual = validate "@hello true\n@hello world",
102
+ errors = validate "@hello true\n@hello world",
107
103
  hello: MetaHeader::BOOLEAN
108
- assert_equal ["tag 'hello' cannot have a value"], actual
104
+ assert_equal ["tag 'hello' cannot have a value"], errors
109
105
  end
110
106
 
111
107
  def test_alias
112
- mh = MetaHeader.new "@a"
108
+ mh = MetaHeader.parse "@a"
113
109
  mh.alias :a, :b
114
110
  assert_equal ["missing value for tag 'a'"],
115
111
  mh.validate(b: [MetaHeader::REQUIRED, MetaHeader::VALUE])
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: metaheader
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3beta3
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - cfillion
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-28 00:00:00.000000000 Z
11
+ date: 2020-10-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -16,73 +16,59 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.10'
19
+ version: '2.0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.10'
27
- - !ruby/object:Gem::Dependency
28
- name: coveralls
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '0.8'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '0.8'
26
+ version: '2.0'
41
27
  - !ruby/object:Gem::Dependency
42
28
  name: minitest
43
29
  requirement: !ruby/object:Gem::Requirement
44
30
  requirements:
45
31
  - - "~>"
46
32
  - !ruby/object:Gem::Version
47
- version: '5.8'
33
+ version: '5.10'
48
34
  type: :development
49
35
  prerelease: false
50
36
  version_requirements: !ruby/object:Gem::Requirement
51
37
  requirements:
52
38
  - - "~>"
53
39
  - !ruby/object:Gem::Version
54
- version: '5.8'
40
+ version: '5.10'
55
41
  - !ruby/object:Gem::Dependency
56
42
  name: rake
57
43
  requirement: !ruby/object:Gem::Requirement
58
44
  requirements:
59
45
  - - "~>"
60
46
  - !ruby/object:Gem::Version
61
- version: '11.0'
47
+ version: '13.0'
62
48
  type: :development
63
49
  prerelease: false
64
50
  version_requirements: !ruby/object:Gem::Requirement
65
51
  requirements:
66
52
  - - "~>"
67
53
  - !ruby/object:Gem::Version
68
- version: '11.0'
54
+ version: '13.0'
69
55
  - !ruby/object:Gem::Dependency
70
56
  name: simplecov
71
57
  requirement: !ruby/object:Gem::Requirement
72
58
  requirements:
73
59
  - - "~>"
74
60
  - !ruby/object:Gem::Version
75
- version: '0.11'
61
+ version: '0.13'
76
62
  type: :development
77
63
  prerelease: false
78
64
  version_requirements: !ruby/object:Gem::Requirement
79
65
  requirements:
80
66
  - - "~>"
81
67
  - !ruby/object:Gem::Version
82
- version: '0.11'
83
- description:
68
+ version: '0.13'
69
+ description:
84
70
  email:
85
- - metaheader@cfillion.tk
71
+ - metaheader@cfillion.ca
86
72
  executables: []
87
73
  extensions: []
88
74
  extra_rdoc_files: []
@@ -101,13 +87,14 @@ files:
101
87
  - test/helper.rb
102
88
  - test/input/basic_tag
103
89
  - test/input/custom_parser
90
+ - test/test_multiline.rb
104
91
  - test/test_parser.rb
105
92
  - test/test_validator.rb
106
93
  homepage: https://github.com/cfillion/metaheader
107
94
  licenses:
108
95
  - LGPL-3.0+
109
96
  metadata: {}
110
- post_install_message:
97
+ post_install_message:
111
98
  rdoc_options: []
112
99
  require_paths:
113
100
  - lib
@@ -118,18 +105,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
118
105
  version: '2.3'
119
106
  required_rubygems_version: !ruby/object:Gem::Requirement
120
107
  requirements:
121
- - - ">"
108
+ - - ">="
122
109
  - !ruby/object:Gem::Version
123
- version: 1.3.1
110
+ version: '0'
124
111
  requirements: []
125
- rubyforge_project:
126
- rubygems_version: 2.5.1
127
- signing_key:
112
+ rubygems_version: 3.1.4
113
+ signing_key:
128
114
  specification_version: 4
129
115
  summary: Parser for metadata headers in plain-text files
130
116
  test_files:
131
117
  - test/helper.rb
132
118
  - test/input/basic_tag
133
119
  - test/input/custom_parser
120
+ - test/test_multiline.rb
134
121
  - test/test_parser.rb
135
122
  - test/test_validator.rb