metaheader 1.3.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/Gemfile +0 -2
- data/README.md +9 -11
- data/lib/metaheader.rb +60 -79
- data/lib/metaheader/version.rb +1 -1
- data/metaheader.gemspec +3 -3
- data/test/test_multiline.rb +16 -16
- data/test/test_parser.rb +75 -104
- data/test/test_validator.rb +38 -42
- metadata +8 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 12c2b91a83ff05bf044e54acbd9b5143fc72fa5e68b08d2b21c6517054626146
|
4
|
+
data.tar.gz: ad3f13076f50ce44745b8ef87d15b1105f189f6081b52a88e749df11464df9cb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7e94db96de5e751cc89fe3bf6f38d12105c278dd47623f6eee6bbb66d4d061b281219e1d0db3df97af6fda8e29da2c07cae9daa7c59eaafa22f834083d79ba91
|
7
|
+
data.tar.gz: 9455e3d58f9a796f662a5cad16225ce5c114e124110ebbfd9015e5163f21c0c34349aff57ad1dc8f609de76416bee3ac2c6c7266d19a791f43c7bd8379d497a5
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -19,7 +19,8 @@
|
|
19
19
|
@key false
|
20
20
|
```
|
21
21
|
|
22
|
-
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):
|
23
24
|
|
24
25
|
```cpp
|
25
26
|
/*
|
@@ -27,13 +28,14 @@ Any kind of comment syntax or prefix can be used:
|
|
27
28
|
*/
|
28
29
|
```
|
29
30
|
|
30
|
-
An alternative syntax is also supported:
|
31
|
+
An alternative syntax that allows spaces in the key name is also supported:
|
31
32
|
|
32
33
|
```
|
33
|
-
Key
|
34
|
+
Key: Value
|
34
35
|
```
|
35
36
|
|
36
|
-
Parsing stops at the first empty line
|
37
|
+
Parsing stops at the first empty line outside of a multiline value
|
38
|
+
(ignoring white space).
|
37
39
|
|
38
40
|
## Usage
|
39
41
|
|
@@ -41,14 +43,11 @@ Parsing stops at the first empty line (ignoring white space).
|
|
41
43
|
require 'metaheader'
|
42
44
|
|
43
45
|
input = '@key value'
|
44
|
-
mh = MetaHeader.
|
46
|
+
mh = MetaHeader.parse input
|
45
47
|
|
46
48
|
# alternatively:
|
47
49
|
# mh = MetaHeader.from_file path
|
48
50
|
|
49
|
-
# mark unknown keys as invalid
|
50
|
-
# mh.strict = true
|
51
|
-
|
52
51
|
# set @key as mandatory
|
53
52
|
errors = mh.validate key: MetaHeader::REQUIRED
|
54
53
|
|
@@ -65,13 +64,12 @@ value = mh[:key]
|
|
65
64
|
|
66
65
|
## Documentation
|
67
66
|
|
68
|
-
MetaHeader's documentation is hosted at
|
69
|
-
[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>.
|
70
68
|
|
71
69
|
## Contributing
|
72
70
|
|
73
71
|
1. [Fork this repository](https://github.com/cfillion/metaheader/fork)
|
74
|
-
2. Create your feature branch (`git
|
72
|
+
2. Create your feature branch (`git switch -c my-new-feature`)
|
75
73
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
76
74
|
4. Push to the branch (`git push -u origin my-new-feature`)
|
77
75
|
5. Create a new Pull Request
|
data/lib/metaheader.rb
CHANGED
@@ -1,84 +1,67 @@
|
|
1
1
|
require 'metaheader/version'
|
2
2
|
|
3
|
+
# Parser for metadata header in plain-text files
|
4
|
+
# @example
|
5
|
+
# mh = MetaHeader.parse '@hello world'
|
6
|
+
# puts mh[:hello]
|
3
7
|
class MetaHeader
|
4
|
-
#
|
5
|
-
|
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
|
18
|
-
|
19
|
-
# @return [MetaHeader] the current instance
|
20
|
-
def header
|
21
|
-
@mh
|
22
|
-
end
|
8
|
+
# Allow a tag to be omitted when validating in strict mode.
|
9
|
+
OPTIONAL = Object.new.freeze
|
23
10
|
|
24
|
-
|
25
|
-
|
26
|
-
def parse(raw_input)
|
27
|
-
raise NotImplementedError
|
28
|
-
end
|
29
|
-
end
|
11
|
+
# Ensure a tag exists during validation.
|
12
|
+
REQUIRED = Object.new.freeze
|
30
13
|
|
14
|
+
# The tag cannot hold a value beside true or false during validation.
|
31
15
|
BOOLEAN = Object.new.freeze
|
32
|
-
|
33
|
-
|
34
|
-
SINGLELINE = Object.new.freeze
|
16
|
+
|
17
|
+
# The tag must have a string value (non-boolean tag) during validation.
|
35
18
|
VALUE = Object.new.freeze
|
36
19
|
|
37
|
-
#
|
38
|
-
|
39
|
-
# @return [Boolean]
|
40
|
-
attr_accessor :strict
|
20
|
+
# Don't allow multiline values during validation.
|
21
|
+
SINGLELINE = Object.new.freeze
|
41
22
|
|
42
23
|
# Create a new instance from the contents of a file.
|
43
24
|
# @param path [String] path to the file to be read
|
44
25
|
# @return [MetaHeader]
|
45
26
|
def self.from_file(path)
|
46
|
-
File.open(path) {|file| self.
|
27
|
+
File.open(path) {|file| self.parse file }
|
47
28
|
end
|
48
29
|
|
49
|
-
# Construct a
|
50
|
-
#
|
30
|
+
# Construct a MetaHeader object and parse every tags found in the input up to
|
31
|
+
# the first newline.
|
32
|
+
# @param input [String, IO, StringIO]
|
51
33
|
# @return [MetaHeader]
|
52
34
|
def self.parse(input)
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
self.new input
|
57
|
-
end
|
35
|
+
mh = MetaHeader.new
|
36
|
+
mh.parse input
|
37
|
+
mh
|
58
38
|
end
|
59
39
|
|
60
|
-
#
|
61
|
-
|
62
|
-
def initialize(input)
|
63
|
-
@strict = false
|
40
|
+
# Construct a blank MetaHeader object
|
41
|
+
def initialize
|
64
42
|
@data = {}
|
43
|
+
end
|
65
44
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
45
|
+
# Parse every tags found in the input up to the first newline.
|
46
|
+
# @param input [String, IO, StringIO]
|
47
|
+
# @return [Integer] Character position of the first content line in the input
|
48
|
+
# data following the header.
|
49
|
+
def parse(input)
|
50
|
+
if input.is_a? String
|
70
51
|
input = StringIO.new input.encode universal_newline: true
|
71
52
|
end
|
72
53
|
|
73
|
-
|
74
|
-
|
75
|
-
Parser.each {|klass|
|
76
|
-
input.rewind
|
54
|
+
@last_tag = nil
|
55
|
+
@empty_lines = 0
|
77
56
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
57
|
+
content_offset = 0
|
58
|
+
input.each_line do |line|
|
59
|
+
full_line_size = line.size # parse_line can trim the line
|
60
|
+
continue = parse_line line
|
61
|
+
content_offset += full_line_size if continue || line.empty?
|
62
|
+
break unless continue
|
63
|
+
end
|
64
|
+
content_offset
|
82
65
|
end
|
83
66
|
|
84
67
|
# Returns the value of a tag by its name, or nil if not found.
|
@@ -95,7 +78,7 @@ class MetaHeader
|
|
95
78
|
|
96
79
|
# Replaces the value of a tag.
|
97
80
|
# @param value the new value
|
98
|
-
# @return value
|
81
|
+
# @return [Object] value
|
99
82
|
def []=(key, value)
|
100
83
|
raise ArgumentError, 'value cannot be nil' if value.nil?
|
101
84
|
|
@@ -129,7 +112,7 @@ class MetaHeader
|
|
129
112
|
end
|
130
113
|
|
131
114
|
# Make a hash from the parsed data
|
132
|
-
# @return [Hash]
|
115
|
+
# @return [Hash<Symbol, Object>]
|
133
116
|
def to_h
|
134
117
|
Hash[@data.map {|name, tag| [name, tag.value] }]
|
135
118
|
end
|
@@ -141,22 +124,25 @@ class MetaHeader
|
|
141
124
|
end
|
142
125
|
|
143
126
|
# Validates parsed data according to a custom set of rules.
|
127
|
+
# A rule can be one of the predefined constants, a regex, a proc or a
|
128
|
+
# method (returing nil if the tag is valid or an error string otherwise).
|
144
129
|
# @example
|
145
130
|
# mh = MetaHeader.new "@hello world\n@chunky bacon"
|
146
131
|
# mh.validate \
|
147
132
|
# hello: [MetaHeader::REQUIRED, MetaHeader::SINGLELINE, /\d/],
|
148
133
|
# chunky: proc {|value| 'not bacon' unless value == 'bacon' }
|
149
|
-
# @param rules [Hash] tag_name => rule or
|
150
|
-
# @
|
151
|
-
# @
|
134
|
+
# @param rules [Hash] :tag_name => rule or [rule1, rule2, ...]
|
135
|
+
# @param strict [Boolean] Whether to report unknown tags as errors
|
136
|
+
# @return [Array<String>] List of error messasges
|
152
137
|
# @see OPTIONAL
|
153
138
|
# @see REQUIRED
|
154
|
-
# @see
|
139
|
+
# @see BOOLEAN
|
155
140
|
# @see VALUE
|
156
|
-
|
157
|
-
|
141
|
+
# @see SINGLELINE
|
142
|
+
def validate(rules, strict = false)
|
143
|
+
errors = []
|
158
144
|
|
159
|
-
if
|
145
|
+
if strict
|
160
146
|
@data.each {|key, tag|
|
161
147
|
errors << "unknown tag '%s'" % tag.name unless rules.has_key? key
|
162
148
|
}
|
@@ -168,27 +154,22 @@ class MetaHeader
|
|
168
154
|
end
|
169
155
|
}
|
170
156
|
|
171
|
-
errors
|
157
|
+
errors
|
172
158
|
end
|
173
159
|
|
174
160
|
# Rename one or more tags.
|
175
|
-
# @param old [Symbol, Hash]
|
176
|
-
# @param new [Symbol]
|
161
|
+
# @param old [Symbol, Array<Symbol>, Hash<Symbol, Symbol>]
|
162
|
+
# @param new [Symbol] Ignored if old is a Hash
|
177
163
|
# @example
|
178
164
|
# mh.alias :old, :new
|
179
|
-
# mh.alias :old1, :old2, :new
|
180
165
|
# mh.alias [:old1, :old2], :new
|
181
166
|
# mh.alias old1: :new1, old2: :new2
|
182
|
-
def alias(
|
183
|
-
|
184
|
-
|
185
|
-
tags, new = args
|
186
|
-
|
187
|
-
if args.size == 1
|
188
|
-
tags.each {|k, v| self.alias k, v }
|
167
|
+
def alias(old, new = nil)
|
168
|
+
if old.is_a? Hash
|
169
|
+
old.each {|k, v| self.alias k, v }
|
189
170
|
else
|
190
|
-
Array(
|
191
|
-
@data[new] = delete
|
171
|
+
Array(old).each {|tag|
|
172
|
+
@data[new] = delete tag if has? tag
|
192
173
|
}
|
193
174
|
end
|
194
175
|
end
|
@@ -202,7 +183,7 @@ private
|
|
202
183
|
(?:\s*(?<value>[^\n]+))?
|
203
184
|
\Z/x.freeze
|
204
185
|
|
205
|
-
def
|
186
|
+
def parse_line(line)
|
206
187
|
line.chomp!
|
207
188
|
line.encode! Encoding::UTF_8, invalid: :replace
|
208
189
|
|
data/lib/metaheader/version.rb
CHANGED
data/metaheader.gemspec
CHANGED
@@ -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.
|
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,8 +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', '~>
|
23
|
+
spec.add_development_dependency 'bundler', '~> 2.0'
|
24
24
|
spec.add_development_dependency 'minitest', '~> 5.10'
|
25
|
-
spec.add_development_dependency 'rake', '~>
|
25
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
26
26
|
spec.add_development_dependency 'simplecov', '~> 0.13'
|
27
27
|
end
|
data/test/test_multiline.rb
CHANGED
@@ -2,7 +2,7 @@ require File.expand_path '../helper', __FILE__
|
|
2
2
|
|
3
3
|
class TestMultiline < MiniTest::Test
|
4
4
|
def test_multiline
|
5
|
-
mh = MetaHeader.
|
5
|
+
mh = MetaHeader.parse <<-IN
|
6
6
|
@test Lorem
|
7
7
|
Ipsum
|
8
8
|
IN
|
@@ -12,7 +12,7 @@ class TestMultiline < MiniTest::Test
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def test_variant
|
15
|
-
mh = MetaHeader.
|
15
|
+
mh = MetaHeader.parse <<-IN
|
16
16
|
@test
|
17
17
|
Lorem
|
18
18
|
Ipsum
|
@@ -22,7 +22,7 @@ class TestMultiline < MiniTest::Test
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def test_trailing_space
|
25
|
-
mh = MetaHeader.
|
25
|
+
mh = MetaHeader.parse <<-IN
|
26
26
|
@hello\x20
|
27
27
|
test\x20
|
28
28
|
@world\x20\x20
|
@@ -34,7 +34,7 @@ class TestMultiline < MiniTest::Test
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def test_prefix
|
37
|
-
mh = MetaHeader.
|
37
|
+
mh = MetaHeader.parse <<-IN
|
38
38
|
-- @test Lorem
|
39
39
|
-- Ipsum
|
40
40
|
IN
|
@@ -44,7 +44,7 @@ class TestMultiline < MiniTest::Test
|
|
44
44
|
end
|
45
45
|
|
46
46
|
def test_no_indent
|
47
|
-
mh = MetaHeader.
|
47
|
+
mh = MetaHeader.parse <<-IN
|
48
48
|
@test Lorem
|
49
49
|
Ipsum
|
50
50
|
Test
|
@@ -55,7 +55,7 @@ class TestMultiline < MiniTest::Test
|
|
55
55
|
end
|
56
56
|
|
57
57
|
def test_lose_indent
|
58
|
-
mh = MetaHeader.
|
58
|
+
mh = MetaHeader.parse <<-IN
|
59
59
|
@test
|
60
60
|
Hello
|
61
61
|
|
@@ -71,7 +71,7 @@ class TestMultiline < MiniTest::Test
|
|
71
71
|
end
|
72
72
|
|
73
73
|
def test_fewer_indent
|
74
|
-
mh = MetaHeader.
|
74
|
+
mh = MetaHeader.parse <<-IN
|
75
75
|
@test Lorem
|
76
76
|
Ipsum
|
77
77
|
Hello
|
@@ -83,7 +83,7 @@ class TestMultiline < MiniTest::Test
|
|
83
83
|
end
|
84
84
|
|
85
85
|
def test_extra_indent
|
86
|
-
mh = MetaHeader.
|
86
|
+
mh = MetaHeader.parse <<-IN
|
87
87
|
@test Lorem
|
88
88
|
Ipsum
|
89
89
|
Hello
|
@@ -95,7 +95,7 @@ class TestMultiline < MiniTest::Test
|
|
95
95
|
end
|
96
96
|
|
97
97
|
def test_do_not_reuse_indent
|
98
|
-
mh = MetaHeader.
|
98
|
+
mh = MetaHeader.parse <<-IN
|
99
99
|
@a
|
100
100
|
Lorem
|
101
101
|
Ipsum
|
@@ -110,7 +110,7 @@ class TestMultiline < MiniTest::Test
|
|
110
110
|
end
|
111
111
|
|
112
112
|
def test_sub_alternate_syntax
|
113
|
-
mh = MetaHeader.
|
113
|
+
mh = MetaHeader.parse <<-IN
|
114
114
|
@test Lorem
|
115
115
|
Ipsum:
|
116
116
|
Dolor: sit amet
|
@@ -121,7 +121,7 @@ class TestMultiline < MiniTest::Test
|
|
121
121
|
end
|
122
122
|
|
123
123
|
def test_explicit_boolean
|
124
|
-
mh = MetaHeader.
|
124
|
+
mh = MetaHeader.parse <<-IN
|
125
125
|
@test true
|
126
126
|
test
|
127
127
|
IN
|
@@ -130,7 +130,7 @@ class TestMultiline < MiniTest::Test
|
|
130
130
|
end
|
131
131
|
|
132
132
|
def test_empty_line_prefix
|
133
|
-
mh = MetaHeader.
|
133
|
+
mh = MetaHeader.parse <<-IN
|
134
134
|
--@test
|
135
135
|
-- Hello
|
136
136
|
--
|
@@ -145,7 +145,7 @@ class TestMultiline < MiniTest::Test
|
|
145
145
|
end
|
146
146
|
|
147
147
|
def test_empty_line_prefix_with_space
|
148
|
-
mh = MetaHeader.
|
148
|
+
mh = MetaHeader.parse <<-IN
|
149
149
|
-- @test
|
150
150
|
-- Hello
|
151
151
|
--
|
@@ -156,7 +156,7 @@ class TestMultiline < MiniTest::Test
|
|
156
156
|
end
|
157
157
|
|
158
158
|
def test_empty_line
|
159
|
-
mh = MetaHeader.
|
159
|
+
mh = MetaHeader.parse <<-IN
|
160
160
|
@test
|
161
161
|
Hello
|
162
162
|
|
@@ -172,7 +172,7 @@ class TestMultiline < MiniTest::Test
|
|
172
172
|
end
|
173
173
|
|
174
174
|
def test_break_at_empty_line
|
175
|
-
mh = MetaHeader.
|
175
|
+
mh = MetaHeader.parse <<-IN
|
176
176
|
-- @hello world
|
177
177
|
|
178
178
|
@chunky bacon
|
@@ -183,7 +183,7 @@ class TestMultiline < MiniTest::Test
|
|
183
183
|
end
|
184
184
|
|
185
185
|
def test_alternate_syntax
|
186
|
-
mh = MetaHeader.
|
186
|
+
mh = MetaHeader.parse <<-IN
|
187
187
|
-- Hello:
|
188
188
|
-- World
|
189
189
|
IN
|
data/test/test_parser.rb
CHANGED
@@ -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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
86
|
+
mh = MetaHeader.parse <<-IN
|
119
87
|
-- @first
|
120
88
|
--
|
121
89
|
-- @second
|
@@ -124,7 +92,7 @@ class TestParser < MiniTest::Test
|
|
124
92
|
refute_nil mh[:second]
|
125
93
|
end
|
126
94
|
|
127
|
-
def
|
95
|
+
def test_from_file
|
128
96
|
path = File.expand_path '../input/basic_tag', __FILE__
|
129
97
|
mh = MetaHeader.from_file path
|
130
98
|
|
@@ -132,134 +100,137 @@ class TestParser < MiniTest::Test
|
|
132
100
|
assert_equal 1, mh.size
|
133
101
|
end
|
134
102
|
|
103
|
+
def test_read_file_stream
|
104
|
+
path = File.expand_path '../input/basic_tag', __FILE__
|
105
|
+
mh = MetaHeader.parse File.open(path)
|
106
|
+
|
107
|
+
assert_equal 'Hello World', mh[:test]
|
108
|
+
assert_equal 1, mh.size
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_read_string_stream
|
112
|
+
stream = StringIO.new '@hello world'
|
113
|
+
MetaHeader.parse stream
|
114
|
+
end
|
115
|
+
|
135
116
|
def test_to_hash
|
136
|
-
mh = MetaHeader.
|
117
|
+
mh = MetaHeader.parse '@key value'
|
137
118
|
assert_equal Hash[key: 'value'], mh.to_h
|
138
119
|
end
|
139
120
|
|
140
121
|
def test_alternate_syntax
|
141
|
-
mh = MetaHeader.
|
122
|
+
mh = MetaHeader.parse 'Key Test: value'
|
142
123
|
assert_equal Hash[key_test: 'value'], mh.to_h
|
143
124
|
end
|
144
125
|
|
145
126
|
def test_alternate_syntax_prefix
|
146
|
-
mh = MetaHeader.
|
127
|
+
mh = MetaHeader.parse '-- Key Test: Value'
|
147
128
|
assert_equal Hash[key_test: 'Value'], mh.to_h
|
148
129
|
end
|
149
130
|
|
150
|
-
def
|
151
|
-
mh = MetaHeader.
|
131
|
+
def test_crlf_newlines
|
132
|
+
mh = MetaHeader.parse "key: value\r\n@run_custom"
|
152
133
|
assert_equal 'value', mh[:key]
|
153
|
-
assert_equal
|
134
|
+
assert_equal true, mh[:run_custom]
|
154
135
|
end
|
155
136
|
|
156
137
|
def test_alternate_syntax_trailing_space
|
157
|
-
mh = MetaHeader.
|
138
|
+
mh = MetaHeader.parse ' Key Test : Value'
|
158
139
|
assert_equal Hash[key_test: 'Value'], mh.to_h
|
159
140
|
end
|
160
141
|
|
161
142
|
def test_alternate_syntax_compact
|
162
|
-
mh = MetaHeader.
|
143
|
+
mh = MetaHeader.parse 'Key Test:Value'
|
163
144
|
assert_equal Hash[key_test: 'Value'], mh.to_h
|
164
145
|
end
|
165
146
|
|
166
147
|
def test_alternate_syntax_no_value
|
167
|
-
mh = MetaHeader.
|
148
|
+
mh = MetaHeader.parse 'Key Test:'
|
168
149
|
assert_equal Hash.new, mh.to_h
|
169
150
|
end
|
170
151
|
|
171
152
|
def test_inspect
|
172
|
-
mh = MetaHeader.
|
153
|
+
mh = MetaHeader.parse '@hello world'
|
173
154
|
|
174
155
|
hash = {hello: 'world'}
|
175
156
|
assert_equal "#<MetaHeader #{hash.inspect}>", mh.inspect
|
176
157
|
end
|
177
158
|
|
178
|
-
def test_default_parser_implementation
|
179
|
-
assert_raises NotImplementedError do
|
180
|
-
MetaHeader::Parser.new.parse String.new
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
def test_transform_from_text
|
185
|
-
input = "@run_custom\nHello\n\nWorld".freeze
|
186
|
-
|
187
|
-
mh = MetaHeader.new input
|
188
|
-
|
189
|
-
assert CustomParser.called?
|
190
|
-
assert_equal input, CustomParser.input
|
191
|
-
assert_same mh, CustomParser.instance
|
192
|
-
end
|
193
|
-
|
194
|
-
def test_transform_from_file
|
195
|
-
path = File.expand_path '../input/custom_parser', __FILE__
|
196
|
-
|
197
|
-
mh = MetaHeader.from_file path
|
198
|
-
assert_equal 'worldworld', mh[:hello]
|
199
|
-
|
200
|
-
assert CustomParser.called?
|
201
|
-
assert_equal File.read(path), CustomParser.input
|
202
|
-
assert_same mh, CustomParser.instance
|
203
|
-
end
|
204
|
-
|
205
159
|
def test_has_tag
|
206
|
-
mh = MetaHeader.
|
160
|
+
mh = MetaHeader.parse '@hello'
|
207
161
|
assert_equal true, mh.has?(:hello)
|
208
162
|
assert_equal false, mh.has?(:world)
|
209
163
|
end
|
210
164
|
|
211
165
|
def test_default_value
|
212
|
-
mh = MetaHeader.
|
166
|
+
mh = MetaHeader.parse String.new
|
213
167
|
assert_equal 'world', mh[:hello, 'world']
|
214
168
|
end
|
215
169
|
|
216
170
|
def test_delete
|
217
|
-
mh = MetaHeader.new
|
171
|
+
mh = MetaHeader.new
|
172
|
+
mh[:hello] = 'world'
|
218
173
|
assert mh.has?(:hello)
|
219
174
|
mh.delete :hello
|
220
175
|
refute mh.has?(:hello)
|
221
176
|
end
|
222
177
|
|
223
|
-
def test_construct_from_instance
|
224
|
-
mh = MetaHeader.new '@hello world'
|
225
|
-
assert_same mh, MetaHeader.parse(mh)
|
226
|
-
assert_equal mh.to_h, MetaHeader.parse('@hello world').to_h
|
227
|
-
end
|
228
|
-
|
229
178
|
def test_alias
|
230
|
-
mh = MetaHeader.new
|
179
|
+
mh = MetaHeader.new
|
180
|
+
mh[:a] = '1'
|
231
181
|
mh.alias :a, :b
|
232
182
|
refute mh.has?(:a)
|
233
183
|
assert_equal '1', mh[:b]
|
234
|
-
|
235
|
-
mh[:d] = '2'
|
236
|
-
mh.alias :c, :d
|
237
|
-
refute mh.has?(:c)
|
238
|
-
assert_equal '2', mh[:d]
|
239
184
|
end
|
240
185
|
|
241
186
|
def test_alias_hash
|
242
|
-
mh = MetaHeader.new
|
187
|
+
mh = MetaHeader.new
|
188
|
+
mh[:a] = '1'
|
189
|
+
mh[:b] = '2'
|
243
190
|
mh.alias a: :c, b: :d
|
244
191
|
assert_equal '1', mh[:c]
|
245
192
|
assert_equal '2', mh[:d]
|
246
193
|
end
|
247
194
|
|
248
195
|
def test_alias_array
|
249
|
-
mh = MetaHeader.new
|
196
|
+
mh = MetaHeader.new
|
197
|
+
mh[:a] = '1'
|
198
|
+
mh[:b] = '2'
|
250
199
|
mh.alias [:a, :b, :c], :d
|
251
200
|
assert [:a, :b, :c].none? {|t| mh.has? t }
|
252
201
|
assert_equal '2', mh[:d]
|
253
202
|
end
|
254
203
|
|
255
|
-
def test_alias_invalid_args
|
256
|
-
mh = MetaHeader.new "@a 1\n@b 2"
|
257
|
-
assert_raises(ArgumentError) { mh.alias }
|
258
|
-
assert_raises(ArgumentError) { mh.alias 1, 2, 3 }
|
259
|
-
end
|
260
|
-
|
261
204
|
def test_utf16_bom
|
262
|
-
mh = MetaHeader.
|
205
|
+
mh = MetaHeader.parse "\xff\xfe@a b\n"
|
263
206
|
assert_equal 'b', mh[:a]
|
264
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
|
235
|
+
end
|
265
236
|
end
|
data/test/test_validator.rb
CHANGED
@@ -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.
|
5
|
+
MetaHeader.parse(input).validate(rules)
|
6
6
|
end
|
7
7
|
|
8
8
|
def test_unknown_strict
|
9
|
-
mh = MetaHeader.
|
10
|
-
mh.
|
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.
|
18
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
36
|
-
assert_equal ["missing tag 'version'"],
|
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.
|
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
|
-
|
50
|
-
assert_equal ["tag 'hello' must be singleline"],
|
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.
|
50
|
+
mh = MetaHeader.parse '@hello'
|
55
51
|
|
56
|
-
|
57
|
-
assert_equal ["missing value for tag 'hello'"],
|
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
|
-
|
62
|
-
assert_equal ["invalid value for tag 'hello'"],
|
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.
|
62
|
+
mh = MetaHeader.parse '@hello'
|
67
63
|
|
68
|
-
|
69
|
-
assert_equal ["invalid value for tag 'hello'"],
|
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
|
-
|
69
|
+
errors = validate '@hello',
|
74
70
|
hello: Proc.new {|value| assert_equal true, value; nil }
|
75
|
-
|
71
|
+
assert_empty errors
|
76
72
|
|
77
|
-
|
73
|
+
errors = validate '@hello world',
|
78
74
|
hello: Proc.new {|value| assert_equal 'world', value; nil }
|
79
|
-
|
75
|
+
assert_empty errors
|
80
76
|
|
81
|
-
|
82
|
-
assert_equal ["invalid value for tag 'hello': Hello World!"],
|
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
|
-
|
87
|
-
assert_equal ["invalid value for tag 'HeLlO'"],
|
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
|
-
|
92
|
-
assert_equal ["invalid value for tag 'hello'"],
|
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
|
-
|
102
|
+
errors = validate "@hello true\n@hello world",
|
107
103
|
hello: MetaHeader::BOOLEAN
|
108
|
-
assert_equal ["tag 'hello' cannot have a value"],
|
104
|
+
assert_equal ["tag 'hello' cannot have a value"], errors
|
109
105
|
end
|
110
106
|
|
111
107
|
def test_alias
|
112
|
-
mh = MetaHeader.
|
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:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- cfillion
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-02-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
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: '
|
26
|
+
version: '2.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: minitest
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '13.0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '13.0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: simplecov
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -68,7 +68,7 @@ dependencies:
|
|
68
68
|
version: '0.13'
|
69
69
|
description:
|
70
70
|
email:
|
71
|
-
- metaheader@cfillion.
|
71
|
+
- metaheader@cfillion.ca
|
72
72
|
executables: []
|
73
73
|
extensions: []
|
74
74
|
extra_rdoc_files: []
|
@@ -109,8 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
111
|
requirements: []
|
112
|
-
|
113
|
-
rubygems_version: 2.6.8
|
112
|
+
rubygems_version: 3.0.2
|
114
113
|
signing_key:
|
115
114
|
specification_version: 4
|
116
115
|
summary: Parser for metadata headers in plain-text files
|