abnf-parsing 0.2.1 → 0.2.2
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 +4 -4
- data/Rakefile +40 -0
- data/lib/abnf.rb +264 -0
- data/test/basics.rb +123 -0
- data/test/url.rb +206 -0
- metadata +11 -9
- data/LICENSE +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 86c1cec234dc9c7ca4cc34f7f61e3f0ad95ee6bf
|
4
|
+
data.tar.gz: 2d711b8d50bedbb0347cf5f07e88370733d7124b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 928b65aac8ade0f89d0651407ed100002e12f2aa56f8c28f16fdfd62db0e7de0cc221cbdc42837ad50d65b5c4bc0bc771a06c8cbc5a87a7f3c963f0df06fcc7d
|
7
|
+
data.tar.gz: ab95d30684c42cf4c837e6a8ae48bb77da5d95c3e1e2ab3373aff86343e26e0ee20b023082db263c7ba90b0bfa02d4c1ac27c646155878c280a5ae21c08d55c1
|
data/Rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'rake'
|
7
|
+
require 'rake/testtask'
|
8
|
+
require 'rubygems/package_task'
|
9
|
+
|
10
|
+
Rake::TestTask.new do |t|
|
11
|
+
t.libs << "test"
|
12
|
+
t.test_files = FileList['test/*.rb']
|
13
|
+
t.verbose = true
|
14
|
+
end
|
15
|
+
|
16
|
+
GEM = "abnf-parsing"
|
17
|
+
|
18
|
+
spec = Gem::Specification.new do |s|
|
19
|
+
s.name = GEM
|
20
|
+
s.version = "0.2.0"
|
21
|
+
s.platform = Gem::Platform::RUBY
|
22
|
+
s.has_rdoc = true
|
23
|
+
s.extra_rdoc_files = ["README", "TODO"]
|
24
|
+
s.summary = "ABNF parsing in Ruby"
|
25
|
+
s.description = "A Ruby library for implementing parsers specified with Augmented Backus Naur Form (ABNF)."
|
26
|
+
s.authors = ["Rob Day", "Ryan Tecco"]
|
27
|
+
s.homepage = "https://github.com/rkday/ruby-abnf"
|
28
|
+
s.email = "ruby-abnf@rkd.me.uk"
|
29
|
+
s.require_path = "lib"
|
30
|
+
s.files = %w{README Rakefile TODO} + Dir.glob("{lib,test}/*")
|
31
|
+
end
|
32
|
+
|
33
|
+
Gem::PackageTask.new(spec) do |pkg|
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
desc "Create the gemspec file."
|
38
|
+
task :gemspec do
|
39
|
+
File.open("#{GEM}.gemspec", "w"){|f| f.puts spec.to_ruby}
|
40
|
+
end
|
data/lib/abnf.rb
ADDED
@@ -0,0 +1,264 @@
|
|
1
|
+
|
2
|
+
# ABNF (RFC 2234) parser objects.
|
3
|
+
|
4
|
+
module ABNF
|
5
|
+
class ::Range
|
6
|
+
def until_end # &blk
|
7
|
+
self.last.times{|i| yield i}
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class RangeWithInfiniteUpperBound
|
12
|
+
def initialize(first)
|
13
|
+
@first = first
|
14
|
+
end
|
15
|
+
|
16
|
+
def first
|
17
|
+
@first
|
18
|
+
end
|
19
|
+
|
20
|
+
def until_end # &blk
|
21
|
+
i = 0
|
22
|
+
|
23
|
+
while true
|
24
|
+
yield i
|
25
|
+
i += 1
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Stream
|
31
|
+
attr_reader :pos
|
32
|
+
|
33
|
+
def initialize(s, full = nil, pos = 0)
|
34
|
+
@s = s
|
35
|
+
@full = full.nil? ? s : full
|
36
|
+
@pos = pos
|
37
|
+
end
|
38
|
+
|
39
|
+
def first
|
40
|
+
@s[0].ord if @s[0]
|
41
|
+
end
|
42
|
+
|
43
|
+
def rest
|
44
|
+
Stream.new(@s[1..-1], @full, @pos + 1)
|
45
|
+
end
|
46
|
+
|
47
|
+
def clip(start)
|
48
|
+
@full[start...@pos]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
#
|
53
|
+
# Helper Classes
|
54
|
+
|
55
|
+
class Satisfies
|
56
|
+
|
57
|
+
# Takes a stream, returns a stream or failure.
|
58
|
+
def match(s)
|
59
|
+
s.rest if predicate(s.first)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class Range < Satisfies
|
64
|
+
def initialize(r)
|
65
|
+
@r = r
|
66
|
+
end
|
67
|
+
|
68
|
+
def predicate(c)
|
69
|
+
@r.member?(c)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
#
|
74
|
+
# Core Operators
|
75
|
+
|
76
|
+
# Remember to place the longest matches first!
|
77
|
+
class Alternate
|
78
|
+
def initialize(*choices, &blk)
|
79
|
+
@choices = choices
|
80
|
+
@blk = blk
|
81
|
+
end
|
82
|
+
|
83
|
+
def set_block(&blk)
|
84
|
+
@blk = blk
|
85
|
+
return self
|
86
|
+
end
|
87
|
+
|
88
|
+
def match(strm)
|
89
|
+
start = strm.pos
|
90
|
+
|
91
|
+
@choices.each {
|
92
|
+
|c|
|
93
|
+
|
94
|
+
n_strm = c.match(strm);
|
95
|
+
|
96
|
+
if n_strm
|
97
|
+
@blk.call(n_strm.clip(start)) unless @blk.nil?
|
98
|
+
|
99
|
+
return n_strm
|
100
|
+
end
|
101
|
+
}
|
102
|
+
|
103
|
+
return nil
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class AlternateChars < Alternate
|
108
|
+
def initialize(string, &blk)
|
109
|
+
@choices = string.each_char.map { |c| Char.new(c) }
|
110
|
+
@blk = blk
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
class Concat
|
115
|
+
def initialize(*choices, &blk)
|
116
|
+
@choices = choices
|
117
|
+
@blk = blk
|
118
|
+
end
|
119
|
+
|
120
|
+
def set_block(&blk)
|
121
|
+
@blk = blk
|
122
|
+
return self
|
123
|
+
end
|
124
|
+
|
125
|
+
def match(strm)
|
126
|
+
c_strm = strm
|
127
|
+
start = c_strm.pos
|
128
|
+
|
129
|
+
@choices.each {
|
130
|
+
|c|
|
131
|
+
|
132
|
+
c_strm = c.match(c_strm)
|
133
|
+
|
134
|
+
return nil if c_strm.nil?
|
135
|
+
}
|
136
|
+
|
137
|
+
@blk.call(c_strm.clip(start)) unless @blk.nil?
|
138
|
+
|
139
|
+
return c_strm
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
class Literal < Concat
|
144
|
+
def initialize(string, &blk)
|
145
|
+
@choices = string.each_char.map { |c| Char.new(c) }
|
146
|
+
@blk = blk
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
class Repetition
|
151
|
+
# Spec: range (between), integer (exact), [:at_most, N], [:at_least, N], :any (zero or more)
|
152
|
+
def initialize(spec, what, &blk)
|
153
|
+
@spec = spec
|
154
|
+
@what = what
|
155
|
+
@blk = blk
|
156
|
+
end
|
157
|
+
|
158
|
+
def set_block(&blk)
|
159
|
+
@blk = blk
|
160
|
+
return self
|
161
|
+
end
|
162
|
+
|
163
|
+
def match(strm)
|
164
|
+
c_strm = strm
|
165
|
+
start = strm.pos
|
166
|
+
|
167
|
+
r = \
|
168
|
+
case @spec
|
169
|
+
when Array # :at_least, :at_most
|
170
|
+
option, i = @spec
|
171
|
+
|
172
|
+
if option == :at_most
|
173
|
+
(0..i)
|
174
|
+
elsif option == :at_least
|
175
|
+
RangeWithInfiniteUpperBound.new(i)
|
176
|
+
end
|
177
|
+
when Integer # Exact
|
178
|
+
@spec..@spec
|
179
|
+
when ::Range # Between
|
180
|
+
@spec
|
181
|
+
when Symbol # Any (zero or more)
|
182
|
+
RangeWithInfiniteUpperBound.new(0)
|
183
|
+
end
|
184
|
+
|
185
|
+
r.until_end {
|
186
|
+
|i|
|
187
|
+
|
188
|
+
tried = @what.match(c_strm)
|
189
|
+
|
190
|
+
if tried.nil?
|
191
|
+
if i < r.first
|
192
|
+
return nil
|
193
|
+
else
|
194
|
+
break
|
195
|
+
end
|
196
|
+
else
|
197
|
+
c_strm = tried
|
198
|
+
end
|
199
|
+
}
|
200
|
+
|
201
|
+
@blk.call(c_strm.clip(start)) unless @blk.nil?
|
202
|
+
|
203
|
+
c_strm
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
class Optional < Repetition
|
208
|
+
def initialize(what)
|
209
|
+
super([:at_most, 1], what)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
class OptionalConcat < Optional
|
214
|
+
def initialize(*what)
|
215
|
+
super(Concat.new(*what))
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
#
|
220
|
+
# Core Rules
|
221
|
+
#
|
222
|
+
# I tried to preserve the RFC names where possible.
|
223
|
+
|
224
|
+
class Char < Satisfies
|
225
|
+
def initialize(c)
|
226
|
+
@char = c.ord
|
227
|
+
end
|
228
|
+
|
229
|
+
def predicate(c)
|
230
|
+
c == @char
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
class Alpha < Alternate; def initialize; super(Range.new(0x41..0x5A), Range.new(0x61..0x7A)) end end
|
235
|
+
class AsciiChar < Range; def initialize; super(0x1..0x7F) end end # char
|
236
|
+
class Bit < Alternate; def initialize; super(Char.new(?0), Char.new(?1)) end end
|
237
|
+
class CR < Char; def initialize; super(0x0D) end end # carriage return
|
238
|
+
class LF < Char; def initialize; super(0x0A) end end # line feed
|
239
|
+
class CRLF < Concat; def initialize; super(CR.new, LF.new) end end # Internet standard newline
|
240
|
+
class Ctl < Alternate; def initialize; super(Range.new(0..0x1F), Char.new(0x7F)) end end # control characters
|
241
|
+
class Digit < Range; def initialize; super(0x30..0x39) end end
|
242
|
+
class DQuote < Char; def initialize; super(0x22) end end # double quote
|
243
|
+
class HTab < Char; def initialize; super(0x9) end end # horizontal tab
|
244
|
+
class SP < Char; def initialize; super(0x20) end end # space
|
245
|
+
class Octet < Range; def initialize; super(0..255) end end # any 8-bit data value
|
246
|
+
class VChar < Range; def initialize; super(0x21..0x7E) end end # visible (printing) characters
|
247
|
+
class WSP < Alternate; def initialize; super(SP.new, HTab.new) end end # whitespace
|
248
|
+
|
249
|
+
class HexDigit < Alternate
|
250
|
+
def initialize
|
251
|
+
super(Digit.new, Range.new(0x41..0x46), Range.new(0x61..0x66))
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
class LWSP < Repetition # Linear white space (past newline)
|
256
|
+
def initialize
|
257
|
+
super(:any, Alternate.new(WSP.new, Concat.new(CRLF.new, WSP.new)))
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def parse(parser, str)
|
262
|
+
parser.match(Stream.new(str))
|
263
|
+
end
|
264
|
+
end
|
data/test/basics.rb
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
$: << File.join(File.dirname(__FILE__), "../lib")
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'abnf'
|
5
|
+
|
6
|
+
class ABNFTest < Test::Unit::TestCase
|
7
|
+
include ABNF
|
8
|
+
|
9
|
+
def assert_blank(s)
|
10
|
+
assert s.nil? || (s.class == String && s.empty?)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_variable_repetition
|
14
|
+
assert parse(Repetition.new(3, Alpha.new), "abcdefg")
|
15
|
+
assert parse(Repetition.new(1, Alpha.new), "a12345")
|
16
|
+
assert_nil parse(Repetition.new(3, Alpha.new), "ab123")
|
17
|
+
assert_nil parse(Repetition.new(1, Alpha.new), "123456")
|
18
|
+
assert parse(Repetition.new(1..3, Alpha.new), "ab1234")
|
19
|
+
assert_nil parse(Repetition.new(3..5, Alpha.new), "ab1234")
|
20
|
+
assert parse(Repetition.new(0, Alpha.new), "12345")
|
21
|
+
assert parse(Repetition.new(0..4, Alpha.new), "a1234")
|
22
|
+
assert parse(Repetition.new(0..1, Alpha.new), "1234")
|
23
|
+
assert parse(Repetition.new(0..1, Alpha.new), "a1234")
|
24
|
+
assert parse(Repetition.new(0..1, Alpha.new), "ab1234")
|
25
|
+
assert parse(Repetition.new([:at_least, 2], Alpha.new){|r| assert_equal "ab", r}, "ab1234")
|
26
|
+
assert parse(Repetition.new([:at_least, 2], Alpha.new){|r| assert_equal "abcde", r}, "abcde12")
|
27
|
+
assert parse(Repetition.new([:at_most, 3], Alpha.new){|r| assert_equal "abc", r}, "abcdefgh")
|
28
|
+
assert parse(Repetition.new([:at_most, 3], Alpha.new), "12345")
|
29
|
+
assert parse(Repetition.new([:at_most, 3], Alpha.new){|r| assert_equal "ab", r}, "ab12345")
|
30
|
+
assert parse(Concat.new(Optional.new(Digit.new), Alpha.new), "a")
|
31
|
+
assert parse(Concat.new(Optional.new(Digit.new), Alpha.new), "1a")
|
32
|
+
assert parse(Repetition.new(:any, Alpha.new), "12345")
|
33
|
+
assert parse(Repetition.new(:any, Alpha.new), "abcdefg12345")
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_single_char_parse
|
37
|
+
parser = Char.new(?-)
|
38
|
+
assert_nil parse(parser, ")")
|
39
|
+
assert parse(parser, "-")
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_alternate_char_parse
|
43
|
+
parser = AlternateChars.new("abcdef")
|
44
|
+
assert_nil parse(parser, ")")
|
45
|
+
assert_nil parse(parser, "-")
|
46
|
+
assert parse(parser, "a")
|
47
|
+
assert parse(parser, "b")
|
48
|
+
assert parse(parser, "c")
|
49
|
+
assert parse(parser, "d")
|
50
|
+
assert parse(parser, "e")
|
51
|
+
assert parse(parser, "f")
|
52
|
+
assert_nil parse(parser, "g")
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_literal_parse
|
56
|
+
parser = Literal.new("abcdef")
|
57
|
+
|
58
|
+
assert parse(parser, "abcdef")
|
59
|
+
|
60
|
+
assert_nil parse(parser, ")")
|
61
|
+
assert_nil parse(parser, "-")
|
62
|
+
assert_nil parse(parser, "a")
|
63
|
+
assert_nil parse(parser, "abcd")
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_set_block
|
67
|
+
output = nil
|
68
|
+
parser = Literal.new("abcdef")
|
69
|
+
|
70
|
+
assert parse(parser, "abcdef")
|
71
|
+
assert_nil output
|
72
|
+
|
73
|
+
parser.set_block {|m| output = m}
|
74
|
+
|
75
|
+
assert parse(parser, "abcdef")
|
76
|
+
|
77
|
+
assert (output == "abcdef")
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_optional_concat_parse
|
81
|
+
# Parser represents ["a" "b"] "c"
|
82
|
+
parser = Concat.new(OptionalConcat.new(Char.new(?a), Char.new(?b)), Char.new(?c))
|
83
|
+
|
84
|
+
assert parse(parser, "abc")
|
85
|
+
assert parse(parser, "c")
|
86
|
+
|
87
|
+
assert_nil parse(parser, ")")
|
88
|
+
assert_nil parse(parser, "-")
|
89
|
+
assert_nil parse(parser, "a")
|
90
|
+
assert_nil parse(parser, "ab")
|
91
|
+
assert_nil parse(parser, "ac")
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_example_1
|
95
|
+
|
96
|
+
# Phone Number
|
97
|
+
p1 = Concat.new(Repetition.new(3, Digit.new), Char.new(?-), Repetition.new(4, Digit.new))
|
98
|
+
assert_nil parse(p1, "22-3452")
|
99
|
+
assert_nil parse(p1, "hello world")
|
100
|
+
assert_nil parse(p1, "4293-603")
|
101
|
+
assert_nil parse(p1, "")
|
102
|
+
assert_nil parse(p1, "4293-3603")
|
103
|
+
assert_nil parse(p1, "734-904-2840")
|
104
|
+
assert parse(p1, "429-3603")
|
105
|
+
|
106
|
+
# Phone Number w/ optional Area Code, same delimiters
|
107
|
+
phone_number = Concat.new(Repetition.new(3, Digit.new), Char.new(?-), Repetition.new(4, Digit.new)){|pn| assert_equal "904-2840", pn}
|
108
|
+
area_code = Concat.new(Repetition.new(3, Digit.new), Char.new(?-))
|
109
|
+
p2 = Alternate.new(Concat.new(area_code, phone_number){|f| assert_equal "734-904-2840", f}, phone_number)
|
110
|
+
assert_nil parse(p2, "22-3452")
|
111
|
+
assert_nil parse(p2, "hello world")
|
112
|
+
assert_nil parse(p2, "4293-603")
|
113
|
+
assert_nil parse(p2, "")
|
114
|
+
assert_nil parse(p2, "4293-3603")
|
115
|
+
assert parse(p2, "904-2840")
|
116
|
+
assert parse(p2, "734-904-2840")
|
117
|
+
|
118
|
+
# Phone Number w/ optional Area Code, different delimiters
|
119
|
+
area_code = Concat.new(Char.new(?(), Repetition.new(3, Digit.new), Char.new(?)))
|
120
|
+
p3 = Concat.new(Optional.new(area_code), phone_number)
|
121
|
+
assert parse(p3, "904-2840")
|
122
|
+
end
|
123
|
+
end
|
data/test/url.rb
ADDED
@@ -0,0 +1,206 @@
|
|
1
|
+
$: << File.join(File.dirname(__FILE__), "../lib")
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'abnf'
|
5
|
+
|
6
|
+
# The rules and state for a URL parser. Taken almost verbatim from RFC 3986.
|
7
|
+
# There is no support for IP-literal, IPvFuture, or IPv6address.
|
8
|
+
# These rules are not implemented: absolute_uri, path, reserved and gen_delims although there is
|
9
|
+
# no functional reason that they couldn't be.
|
10
|
+
|
11
|
+
class URLParser
|
12
|
+
include ABNF
|
13
|
+
|
14
|
+
URL = Struct.new("URL", :fragment, :host, :path, :port, :scheme, :user)
|
15
|
+
|
16
|
+
# API
|
17
|
+
|
18
|
+
def parse(str)
|
19
|
+
@result = Struct::URL.new
|
20
|
+
|
21
|
+
if uri_reference.match(Stream.new(str))
|
22
|
+
@result
|
23
|
+
else
|
24
|
+
raise RuntimeError.new("Parse error")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Rules
|
29
|
+
|
30
|
+
def scheme
|
31
|
+
Concat.new(Alpha.new, Repetition.new(:any, Alternate.new(Alpha.new, Digit.new,
|
32
|
+
Char.new(?+), Char.new(?-), Char.new(?.)))){|s| @result.scheme = s}
|
33
|
+
end
|
34
|
+
|
35
|
+
def dec_octet
|
36
|
+
Alternate.new(Concat.new(Char.new(?2), Char.new(?5), Range.new(0x30..0x35)), # 250-255
|
37
|
+
Concat.new(Char.new(?2), Range.new(0x30..0x34), Digit.new), # 200-249
|
38
|
+
Concat.new(Char.new(?1), Repetition.new(2, Digit.new)), # 100-199
|
39
|
+
Concat.new(Range.new(0x31..0x39), Digit.new), # 10-99
|
40
|
+
Digit.new) # 0-9
|
41
|
+
end
|
42
|
+
|
43
|
+
def pct_encoded
|
44
|
+
Concat.new(Char.new(?%), HexDigit.new, HexDigit.new)
|
45
|
+
end
|
46
|
+
|
47
|
+
def sub_delims
|
48
|
+
Alternate.new(Char.new(?!), Char.new(?$), Char.new(?&), Char.new(?'), Char.new(?(),
|
49
|
+
Char.new(?)), Char.new(?*), Char.new(?+), Char.new(?,), Char.new(?;), Char.new(?=))
|
50
|
+
end
|
51
|
+
|
52
|
+
def unreserved
|
53
|
+
Alternate.new(Alpha.new, Digit.new, Char.new(?-), Char.new(?.), Char.new(?_), Char.new(?~))
|
54
|
+
end
|
55
|
+
|
56
|
+
def pchar
|
57
|
+
Alternate.new(unreserved, pct_encoded, sub_delims, Char.new(?:), Char.new(?@))
|
58
|
+
end
|
59
|
+
|
60
|
+
def ipv4_address
|
61
|
+
Concat.new(dec_octet, Char.new(?.), dec_octet, Char.new(?.), dec_octet, Char.new(?.), dec_octet)
|
62
|
+
end
|
63
|
+
|
64
|
+
def reg_name
|
65
|
+
Repetition.new(:any, Alternate.new(unreserved, pct_encoded, sub_delims))
|
66
|
+
end
|
67
|
+
|
68
|
+
def host
|
69
|
+
Alternate.new(ipv4_address, reg_name){|host| @result.host = host}
|
70
|
+
end
|
71
|
+
|
72
|
+
def port
|
73
|
+
Repetition.new(:any, Digit.new){|port| @result.port = port.to_i}
|
74
|
+
end
|
75
|
+
|
76
|
+
def userinfo
|
77
|
+
Repetition.new(:any, Alternate.new(unreserved, pct_encoded, sub_delims, Char.new(?:))){|u| @result.user = u}
|
78
|
+
end
|
79
|
+
|
80
|
+
def authority
|
81
|
+
Concat.new(Optional.new(Concat.new(userinfo, Char.new(?@))), host, Optional.new(Concat.new(Char.new(?:), port)))
|
82
|
+
end
|
83
|
+
|
84
|
+
def segment
|
85
|
+
Repetition.new(:any, pchar)
|
86
|
+
end
|
87
|
+
|
88
|
+
def segment_nz
|
89
|
+
Repetition.new([:at_least, 1], pchar)
|
90
|
+
end
|
91
|
+
|
92
|
+
def segment_nz_nc # non-zero-length segment without any colon
|
93
|
+
Repetition.new([:at_least, 1], Alternate.new(unreserved, pct_encoded, sub_delims, Char.new(?@)))
|
94
|
+
end
|
95
|
+
|
96
|
+
def path_abempty
|
97
|
+
Repetition.new(:any, Concat.new(Char.new(?/), segment)){|path| @result.path = path}
|
98
|
+
end
|
99
|
+
|
100
|
+
def path_absolute
|
101
|
+
Concat.new(Char.new(?/),
|
102
|
+
Optional.new(Concat.new(segment_nz, Repetition.new(:any, Concat.new(Char.new(?/), segment))))){|path| @result.path = path}
|
103
|
+
end
|
104
|
+
|
105
|
+
def path_empty
|
106
|
+
Repetition.new(0, pchar){|path| @result.path = path}
|
107
|
+
end
|
108
|
+
|
109
|
+
def path_noscheme
|
110
|
+
Concat.new(segment_nz_nc, Repetition.new(:any, Concat.new(Char.new(?/), segment)))
|
111
|
+
end
|
112
|
+
|
113
|
+
def path_rootless
|
114
|
+
Concat.new(segment_nz, Repetition.new(:any, Concat.new(Char.new(?/), segment))){|path| @result.path = path}
|
115
|
+
end
|
116
|
+
|
117
|
+
def hier_part
|
118
|
+
Alternate.new(Concat.new(Char.new(?/), Char.new(?/), authority, path_abempty),
|
119
|
+
path_absolute,
|
120
|
+
path_rootless,
|
121
|
+
path_empty)
|
122
|
+
end
|
123
|
+
|
124
|
+
def query
|
125
|
+
Repetition.new(:any, Alternate.new(pchar, Char.new(?/), Char.new(??))){|query| puts query}
|
126
|
+
end
|
127
|
+
|
128
|
+
def fragment
|
129
|
+
Repetition.new(:any, Alternate.new(pchar, Char.new(?/), Char.new(??))){|fragment| @result.fragment = fragment}
|
130
|
+
end
|
131
|
+
|
132
|
+
def uri
|
133
|
+
Concat.new(scheme,
|
134
|
+
Char.new(?:),
|
135
|
+
hier_part,
|
136
|
+
Optional.new(Concat.new(Char.new(??), query)),
|
137
|
+
Optional.new(Concat.new(Char.new(?#), fragment)))
|
138
|
+
end
|
139
|
+
|
140
|
+
def relative_part
|
141
|
+
Alternate.new(Concat.new(Char.new(?/), Char.new(?/), authority, path_abempty),
|
142
|
+
path_absolute,
|
143
|
+
path_noscheme,
|
144
|
+
path_empty)
|
145
|
+
end
|
146
|
+
|
147
|
+
def relative_ref
|
148
|
+
Concat.new(relative_part,
|
149
|
+
Optional.new(Concat.new(Char.new(??), query)),
|
150
|
+
Optional.new(Concat.new(Char.new(?#), fragment)))
|
151
|
+
end
|
152
|
+
|
153
|
+
def uri_reference
|
154
|
+
Alternate.new(uri, relative_ref)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
class ABNFTest < Test::Unit::TestCase
|
159
|
+
include ABNF
|
160
|
+
|
161
|
+
def test_url_parser
|
162
|
+
|
163
|
+
#
|
164
|
+
# URLs
|
165
|
+
|
166
|
+
u = URLParser.new.parse("http://karmalab.org")
|
167
|
+
assert u
|
168
|
+
assert_equal "http", u.scheme
|
169
|
+
assert_equal "karmalab.org", u.host
|
170
|
+
assert_nil u.port
|
171
|
+
assert_equal "", u.path
|
172
|
+
|
173
|
+
u = URLParser.new.parse("http://rt@karmalab.org:9001")
|
174
|
+
assert u
|
175
|
+
assert_equal "http", u.scheme
|
176
|
+
assert_equal "karmalab.org", u.host
|
177
|
+
assert_equal 9001, u.port
|
178
|
+
assert_equal "rt", u.user
|
179
|
+
assert_equal "", u.path
|
180
|
+
|
181
|
+
u = URLParser.new.parse("file:///tmp/file.txt")
|
182
|
+
assert u
|
183
|
+
assert_equal "file", u.scheme
|
184
|
+
assert_equal "/tmp/file.txt", u.path
|
185
|
+
assert_blank u.host
|
186
|
+
assert_blank u.port
|
187
|
+
assert_blank u.user
|
188
|
+
|
189
|
+
u = URLParser.new.parse("http://triggit.com/j?u=rt10880&p=http://lambda/#fragment16")
|
190
|
+
assert u
|
191
|
+
assert_equal "http", u.scheme
|
192
|
+
assert_equal "triggit.com", u.host
|
193
|
+
assert_equal "/j", u.path
|
194
|
+
assert_equal "fragment16", u.fragment
|
195
|
+
|
196
|
+
u = URLParser.new.parse("about:config")
|
197
|
+
assert u
|
198
|
+
assert_equal "about", u.scheme
|
199
|
+
assert_equal "config", u.path
|
200
|
+
|
201
|
+
u = URLParser.new.parse("/path/to/file.html#fr23-1")
|
202
|
+
assert u
|
203
|
+
assert_equal "/path/to/file.html", u.path
|
204
|
+
assert_equal "fr23-1", u.fragment
|
205
|
+
end
|
206
|
+
end
|
metadata
CHANGED
@@ -1,30 +1,32 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: abnf-parsing
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
- Ryan Tecco
|
8
7
|
- Rob Day
|
8
|
+
- Ryan Tecco
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-
|
12
|
+
date: 2014-03-09 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: A Ruby library for implementing parsers specified with Augmented Backus
|
15
15
|
Naur Form (ABNF).
|
16
|
-
email:
|
16
|
+
email: ruby-abnf@rkd.me.uk
|
17
17
|
executables: []
|
18
18
|
extensions: []
|
19
19
|
extra_rdoc_files:
|
20
20
|
- README
|
21
21
|
- TODO
|
22
|
-
- LICENSE
|
23
22
|
files:
|
24
23
|
- README
|
24
|
+
- Rakefile
|
25
25
|
- TODO
|
26
|
-
-
|
27
|
-
|
26
|
+
- lib/abnf.rb
|
27
|
+
- test/basics.rb
|
28
|
+
- test/url.rb
|
29
|
+
homepage: https://github.com/rkday/ruby-abnf
|
28
30
|
licenses:
|
29
31
|
- MIT
|
30
32
|
metadata: {}
|
@@ -46,6 +48,6 @@ requirements: []
|
|
46
48
|
rubyforge_project:
|
47
49
|
rubygems_version: 2.1.11
|
48
50
|
signing_key:
|
49
|
-
specification_version:
|
50
|
-
summary:
|
51
|
+
specification_version: 4
|
52
|
+
summary: ABNF parsing in Ruby
|
51
53
|
test_files: []
|
data/LICENSE
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
2
|
-
of this software and associated documentation files (the "Software"), to deal
|
3
|
-
in the Software without restriction, including without limitation the rights
|
4
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
5
|
-
copies of the Software, and to permit persons to whom the Software is
|
6
|
-
furnished to do so, subject to the following conditions:
|
7
|
-
|
8
|
-
The above copyright notice and this permission notice shall be included in
|
9
|
-
all copies or substantial portions of the Software.
|
10
|
-
|
11
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
12
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
13
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
14
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
15
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
16
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
17
|
-
THE SOFTWARE.
|