metaheader 1.3beta3 → 2.0.1

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