abnf-parsing 0.2.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 +15 -0
- data/README +25 -0
- data/Rakefile +40 -0
- data/TODO +4 -0
- data/lib/abnf.rb +264 -0
- data/test/basics.rb +123 -0
- data/test/url.rb +206 -0
- metadata +52 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
Y2Y2ZTliMTljZGZlYTA3YTJiOWUwMjJkM2FiOWFjM2NmYTFhNDY2OQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NjM4MzZjYjlkOTNkMWE2MWJjMTIxMDE4ODM3MmFjMTAyNzFhNGZkNA==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NTJhMWUxZmI4YzBmNGI4MWQzZjc3NjhlODI3NDVjOWE3YmM2NTA3NDBkYzdh
|
10
|
+
OWQ5MDQzNzI1ZDY3ZjQ1ZTcyNGUyZjYzN2ZhOWU5N2U1ZTFiYmNjYmU4ZTE3
|
11
|
+
NjQzODdhZjQ2ZmJhOTk4NzJmZTdlOWViNjRhMjc4OWY2OTgxOTQ=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NzAzMzg5MjBlMzk4ODIwYmFlN2JkYzJkMDI5ZTAyYTMxYWNlOWRhODg2MzUy
|
14
|
+
MjQwMjZjYzEyNTI5OTA2MzYyZGVkZjg0NGFjMzA1MzA5YTFmZDUyMTA4MjIx
|
15
|
+
MTZmY2YwMDQ5ZjYwODgwNGJlNDc1MWNiMTA5MzhlMTkzNjYzYjE=
|
data/README
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
|
2
|
+
== Overview ==
|
3
|
+
|
4
|
+
This is a simple library for easily implementing parsers that are specified in Augmented Backus-Naur Form (ABNF).
|
5
|
+
Many IETF RFCs specify protocols and other entities using ABNF. This library lets you use those core operators and
|
6
|
+
rules almost directly as Ruby objects. An optional block can be specified for a rule. If the rule matches, it will
|
7
|
+
pass the match into the block. This makes it possible to build up state as a parse progresses.
|
8
|
+
|
9
|
+
This version of the library was written to RFC 2234, which is an obsoleted version of the RFC. Not too much
|
10
|
+
changed in the subsequent versions however. I'm hoping to do a pass through soon and make sure this library
|
11
|
+
is compliant with RFC 5234.
|
12
|
+
|
13
|
+
Check out: http://tools.ietf.org/html/rfc5234 for more information.
|
14
|
+
|
15
|
+
== Example ==
|
16
|
+
|
17
|
+
For a detailed example, tests/url.rb contains a full URL parser using the ABNF grammar from RFC 3986. It parses
|
18
|
+
a URL from a string returns a structure containing the components.
|
19
|
+
|
20
|
+
== Caveats ==
|
21
|
+
|
22
|
+
* There's no lookahead, so when using Alternate, specify your longest matches first if one of the smaller alternative
|
23
|
+
matches could potentially fire.
|
24
|
+
|
25
|
+
* It works on an 8-bit character basis - at least for now.
|
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/TODO
ADDED
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
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: abnf-parsing
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Rob Day
|
8
|
+
- Ryan Tecco
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-03-09 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: A Ruby library for implementing parsers specified with Augmented Backus
|
15
|
+
Naur Form (ABNF).
|
16
|
+
email: ruby-abnf@rkd.me.uk
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files:
|
20
|
+
- README
|
21
|
+
- TODO
|
22
|
+
files:
|
23
|
+
- README
|
24
|
+
- Rakefile
|
25
|
+
- TODO
|
26
|
+
- lib/abnf.rb
|
27
|
+
- test/basics.rb
|
28
|
+
- test/url.rb
|
29
|
+
homepage: https://github.com/rkday/ruby-abnf
|
30
|
+
licenses: []
|
31
|
+
metadata: {}
|
32
|
+
post_install_message:
|
33
|
+
rdoc_options: []
|
34
|
+
require_paths:
|
35
|
+
- lib
|
36
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
requirements: []
|
47
|
+
rubyforge_project:
|
48
|
+
rubygems_version: 2.2.2
|
49
|
+
signing_key:
|
50
|
+
specification_version: 4
|
51
|
+
summary: ABNF parsing in Ruby
|
52
|
+
test_files: []
|