earl 0.1.0 → 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.
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Earl
2
2
 
3
- TODO: Write a gem description
3
+ What URI wishes it could look like.
4
4
 
5
5
  ## Installation
6
6
 
@@ -18,7 +18,19 @@ Or install it yourself as:
18
18
 
19
19
  ## Usage
20
20
 
21
- TODO: Write usage instructions here
21
+ ``` rb
22
+ url = Earl::URL.new 'http://www.foo.com'
23
+
24
+ url.scheme # => 'http'
25
+ url.scheme? # => true
26
+
27
+ url.subdomain # => 'www'
28
+ url.subdomain.www? # => true
29
+ url.subdomain.baz? # => false
30
+
31
+ url.host = 'foo.edu'
32
+ url.to_s # => 'http://www.foo.edu'
33
+ ```
22
34
 
23
35
  ## Contributing
24
36
 
@@ -15,5 +15,7 @@ Gem::Specification.new do |gem|
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = Earl::VERSION
17
17
 
18
+ gem.add_dependency 'treetop', '>= 1.4.10'
19
+
18
20
  gem.add_development_dependency 'rspec', '>= 2.9.0'
19
21
  end
@@ -1,7 +1,11 @@
1
+ require 'treetop'
1
2
  require 'earl/version'
3
+ require 'earl/url_parser'
2
4
 
3
5
  module Earl
4
6
  autoload :URL, 'earl/url'
7
+ autoload :UrlAssembler, 'earl/url_assembler'
8
+ autoload :HashInquirer, 'earl/hash_inquirer'
5
9
  autoload :StringInquirer, 'earl/string_inquirer'
6
10
 
7
11
  class << self
@@ -9,4 +13,7 @@ module Earl
9
13
  Earl::URL.new string
10
14
  end
11
15
  end
16
+
17
+ class EarlError < StandardError; end
18
+ class InvalidURLError < EarlError; end
12
19
  end
@@ -0,0 +1,16 @@
1
+ module Earl
2
+ class HashInquirer < ::Hash
3
+ def initialize( hash, &block )
4
+ merge! hash
5
+ super block
6
+ end
7
+
8
+ def method_missing( meth, *args, &block )
9
+ if meth.to_s[ -1 ] == '?'
10
+ self[ meth.to_s[ 0..-2 ].to_sym ] || false
11
+ else
12
+ super
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,78 +1,35 @@
1
- require 'uri'
2
-
3
1
  module Earl
4
- class URL
2
+ class URL < HashInquirer
5
3
  def initialize( source )
6
- @uri = URI.parse( source )
7
- end
8
-
9
- def scheme
10
- StringInquirer.new( parts[ :scheme ] ) rescue nil
11
- end
12
- def scheme=( value )
13
- parts[ :scheme ] = value
14
- end
15
- def scheme?
16
- !( parts[ :scheme ].nil? || parts[ :scheme ].empty? )
4
+ super parser.parse( source ).resolve rescue raise InvalidURLError
17
5
  end
18
6
 
19
- def subdomain
20
- StringInquirer.new( parts[ :subdomain ] ) rescue nil
21
- end
22
- def subdomain=( value )
23
- parts[ :subdomain ] = value
24
- end
25
- def subdomain?
26
- !( parts[ :subdomain ].nil? || parts[ :subdomain ].empty? )
27
- end
28
-
29
- def host
30
- StringInquirer.new( [ domain, top_level_domain ].compact.join( '.' ) ) rescue nil
31
- end
32
- def host=( value )
33
- parts[ :domain ], parts[ :top_level_domain ] = value.split( '.' )
34
- end
35
- def host?
36
- [ domain, top_level_domain ].compact.any?
7
+ %w| scheme subdomain host port path search |.each do |part|
8
+ define_method :"#{part}" do
9
+ if self[ part.to_sym ].is_a? String
10
+ StringInquirer.new self[ part.to_sym ]
11
+ else
12
+ self[ part.to_sym ]
13
+ end
14
+ end
15
+ define_method :"#{part}=" do |value|
16
+ raise InvalidURLError if part.to_sym == :host && value == nil
17
+ self[ part.to_sym ] = value
18
+ end
37
19
  end
38
20
 
39
21
  def to_s
40
- out = ''
41
- out << "#{scheme}://" if scheme?
42
- out << [ subdomain, domain, top_level_domain ].compact.join( '.' )
22
+ assembler.assemble self
43
23
  end
44
24
 
45
25
  protected
46
26
 
47
- def domain
48
- StringInquirer.new( parts[ :domain ] ) rescue nil
49
- end
50
- def top_level_domain
51
- StringInquirer.new( parts[ :top_level_domain ] ) rescue nil
27
+ def parser
28
+ @parser ||= UrlParser.new
52
29
  end
53
30
 
54
- def parts
55
- @parts ||= begin
56
- { }.tap do |hsh|
57
- hsh[ :scheme ] = @uri.scheme
58
- hsh[ :subdomain ], hsh[ :domain ], hsh[ :top_level_domain ] = domain_parts
59
- end
60
- end
61
- end
62
-
63
- def domain_parts
64
- parts = host_parts || [ nil ]
65
- parts << nil if parts.size == 1
66
- parts.unshift nil if parts.size == 2
67
- parts
68
- end
69
-
70
- def host_parts
71
- if @uri.host
72
- @uri.host.split '.'
73
- elsif @uri.path
74
- @uri.path.split '.'
75
- end
31
+ def assembler
32
+ @assembler ||= UrlAssembler.new
76
33
  end
77
34
  end
78
35
  end
@@ -0,0 +1,16 @@
1
+ module Earl
2
+ class UrlAssembler
3
+
4
+ def assemble( parts={} )
5
+ ''.tap do |url|
6
+ url << ( parts[ :scheme ] + '://' ) if parts[ :scheme ]
7
+ url << ( parts[ :subdomain ] + '.' ) if parts[ :subdomain ]
8
+ url << ( parts[ :host ] ) if parts[ :host ]
9
+ url << ( ':' + parts[ :port ] ) if parts[ :port ]
10
+ url << ( '/' + parts[ :path ] ) if parts[ :path ]
11
+ url << ( '?' + parts[ :search ] ) if parts[ :search ]
12
+ url
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,163 @@
1
+ module Earl
2
+ grammar Url
3
+
4
+ rule program
5
+ whitespace v:( url ) whitespace {
6
+ def resolve
7
+ { }.merge v.resolve
8
+ end
9
+ }
10
+ end
11
+
12
+ rule whitespace
13
+ [\s]*
14
+ end
15
+
16
+ rule url
17
+ scheme host port path search {
18
+ def resolve
19
+ scheme.resolve.merge port.resolve.merge host.resolve.merge path.resolve.merge search.resolve
20
+ end
21
+ }
22
+ /
23
+ scheme host port path {
24
+ def resolve
25
+ scheme.resolve.merge port.resolve.merge host.resolve.merge path.resolve
26
+ end
27
+ }
28
+ /
29
+ scheme host port search {
30
+ def resolve
31
+ scheme.resolve.merge port.resolve.merge host.resolve.merge search.resolve
32
+ end
33
+ }
34
+ /
35
+ scheme host port {
36
+ def resolve
37
+ scheme.resolve.merge port.resolve.merge host.resolve
38
+ end
39
+ }
40
+ /
41
+ scheme host path search {
42
+ def resolve
43
+ scheme.resolve.merge host.resolve.merge path.resolve.merge search.resolve
44
+ end
45
+ }
46
+ /
47
+ scheme host path {
48
+ def resolve
49
+ scheme.resolve.merge host.resolve.merge path.resolve
50
+ end
51
+ }
52
+ /
53
+ scheme host search {
54
+ def resolve
55
+ scheme.resolve.merge host.resolve.merge search.resolve
56
+ end
57
+ }
58
+ /
59
+ scheme host {
60
+ def resolve
61
+ scheme.resolve.merge host.resolve
62
+ end
63
+ }
64
+ /
65
+ host port path search {
66
+ def resolve
67
+ port.resolve.merge host.resolve.merge path.resolve.merge search.resolve
68
+ end
69
+ }
70
+ /
71
+ host port path {
72
+ def resolve
73
+ port.resolve.merge host.resolve.merge path.resolve
74
+ end
75
+ }
76
+ /
77
+ host port search {
78
+ def resolve
79
+ port.resolve.merge host.resolve.merge search.resolve
80
+ end
81
+ }
82
+ /
83
+ host port {
84
+ def resolve
85
+ port.resolve.merge host.resolve
86
+ end
87
+ }
88
+ /
89
+ host path {
90
+ def resolve
91
+ host.resolve.merge path.resolve
92
+ end
93
+ }
94
+ /
95
+ host search {
96
+ def resolve
97
+ host.resolve.merge search.resolve
98
+ end
99
+ }
100
+ /
101
+ host
102
+ end
103
+
104
+ rule scheme
105
+ characters '://' {
106
+ def resolve
107
+ { :scheme => characters.text_value }
108
+ end
109
+ }
110
+ end
111
+
112
+ rule host
113
+ subdomain:characters '.' domain:characters '.' tld:characters {
114
+ def resolve
115
+ {
116
+ :subdomain => subdomain.text_value,
117
+ :host => "#{domain.text_value}.#{tld.text_value}"
118
+ }
119
+ end
120
+ }
121
+ /
122
+ domain:characters '.' tld:characters {
123
+ def resolve
124
+ { :host => text_value }
125
+ end
126
+ }
127
+ /
128
+ characters {
129
+ def resolve
130
+ { :host => text_value }
131
+ end
132
+ }
133
+ end
134
+
135
+ rule port
136
+ ':' port:([0-9]1..4) {
137
+ def resolve
138
+ { :port => port.text_value }
139
+ end
140
+ }
141
+ end
142
+
143
+ rule path
144
+ '/' characters {
145
+ def resolve
146
+ { :path => characters.text_value }
147
+ end
148
+ }
149
+ end
150
+
151
+ rule search
152
+ '?' search:( characters '=' characters ) {
153
+ def resolve
154
+ { :search => search.text_value }
155
+ end
156
+ }
157
+ end
158
+
159
+ rule characters
160
+ [a-zA-Z0-9]+
161
+ end
162
+ end
163
+ end
@@ -1,3 +1,3 @@
1
1
  module Earl
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe Earl::HashInquirer do
4
+ subject { Earl::HashInquirer.new :foo => 'bar', :baz => 123, :woo => false }
5
+
6
+ it { should be_a( Hash ) }
7
+ it { should eql( :foo => 'bar', :baz => 123, :woo => false ) }
8
+
9
+ describe 'string keys' do
10
+ its( :foo? ){ should be_true }
11
+ end
12
+
13
+ describe 'numeric keys' do
14
+ its( :baz? ){ should be_true }
15
+ end
16
+
17
+ describe 'boolean keys' do
18
+ its( :woo? ){ should be_false }
19
+ end
20
+
21
+ describe 'nonexistant keys' do
22
+ its( :sup? ){ should be_false }
23
+ end
24
+ end
@@ -4,7 +4,7 @@ describe Earl do
4
4
 
5
5
  describe '#parse' do
6
6
  it 'should return an Earl::URL' do
7
- Earl.parse( '' ).should be_an( Earl::URL )
7
+ Earl.parse( 'www.foo.com' ).should be_an( Earl::URL )
8
8
  end
9
9
  end
10
10
  end
@@ -68,12 +68,23 @@ describe Earl::URL do
68
68
 
69
69
  context 'when not provided' do
70
70
  it 'should raise an error' do
71
- expect { Earl::URL.new 'http://' }.to raise_error( URI::InvalidURIError )
71
+ expect { Earl::URL.new( 'http://' ).host }.to raise_error( Earl::InvalidURLError )
72
72
  end
73
73
  end
74
74
  end
75
75
 
76
76
  describe '#path' do
77
- pending
77
+ context 'when provided' do
78
+ subject { Earl::URL.new 'http://www.foo.com/bar' }
79
+ its( :path ){ should eq( 'bar' ) }
80
+ its( :path? ){ should be_true }
81
+ end
82
+
83
+ context 'when not provided' do
84
+ subject { Earl::URL.new 'http://www.foo.com' }
85
+
86
+ its( :path ){ should eq( nil ) }
87
+ its( :path? ){ should be_false }
88
+ end
78
89
  end
79
90
  end
@@ -0,0 +1,189 @@
1
+ require 'spec_helper'
2
+
3
+ describe Earl do
4
+ let( :parser ){ Earl::UrlParser.new }
5
+ let( :assembler ){ Earl::UrlAssembler.new }
6
+
7
+ [
8
+ [ 'localhost', {
9
+ :host => 'localhost'
10
+ } ],
11
+ [ 'foo.com', {
12
+ :host => 'foo.com'
13
+ } ],
14
+
15
+ [ 'foo.edu', {
16
+ :host => 'foo.edu'
17
+ } ],
18
+
19
+ [ 'foo2bar.biz', {
20
+ :host => 'foo2bar.biz'
21
+ } ],
22
+
23
+ [ 'www.foo.com', {
24
+ :host => 'foo.com',
25
+ :subdomain => 'www'
26
+ } ],
27
+
28
+ [ 'http://localhost', {
29
+ :scheme => 'http',
30
+ :host => 'localhost'
31
+ } ],
32
+
33
+ [ 'http://foo.com', {
34
+ :scheme => 'http',
35
+ :host => 'foo.com'
36
+ } ],
37
+
38
+ [ 'http://www.foo.com', {
39
+ :scheme => 'http',
40
+ :host => 'foo.com',
41
+ :subdomain => 'www'
42
+ } ],
43
+
44
+ [ 'localhost:3000', {
45
+ :host => 'localhost',
46
+ :port => '3000'
47
+ } ],
48
+
49
+ [ 'http://localhost:3000', {
50
+ :scheme => 'http',
51
+ :host => 'localhost',
52
+ :port => '3000'
53
+ } ],
54
+
55
+ [ 'www.foo.com:8080', {
56
+ :subdomain => 'www',
57
+ :host => 'foo.com',
58
+ :port => '8080'
59
+ } ],
60
+
61
+ [ 'http://www.foo.com:8080', {
62
+ :scheme => 'http',
63
+ :subdomain => 'www',
64
+ :host => 'foo.com',
65
+ :port => '8080'
66
+ } ],
67
+
68
+ [ 'localhost/bar', {
69
+ :host => 'localhost',
70
+ :path => 'bar'
71
+ } ],
72
+
73
+ [ 'foo.com/bar', {
74
+ :host => 'foo.com',
75
+ :path => 'bar'
76
+ } ],
77
+
78
+ [ 'www.foo.com/bar', {
79
+ :subdomain => 'www',
80
+ :host => 'foo.com',
81
+ :path => 'bar'
82
+ } ],
83
+
84
+ [ 'http://localhost/bar', {
85
+ :scheme => 'http',
86
+ :host => 'localhost',
87
+ :path => 'bar'
88
+ } ],
89
+
90
+ [ 'http://foo.com/bar', {
91
+ :scheme => 'http',
92
+ :host => 'foo.com',
93
+ :path => 'bar'
94
+ } ],
95
+
96
+ [ 'http://www.foo.com/bar', {
97
+ :scheme => 'http',
98
+ :subdomain => 'www',
99
+ :host => 'foo.com',
100
+ :path => 'bar'
101
+ } ],
102
+
103
+ [ 'localhost?baz=woo', {
104
+ :host => 'localhost',
105
+ :search => 'baz=woo'
106
+ } ],
107
+
108
+ [ 'localhost:3000?baz=woo', {
109
+ :host => 'localhost',
110
+ :port => '3000',
111
+ :search => 'baz=woo'
112
+ } ],
113
+
114
+ [ 'localhost:3000/bar?baz=woo', {
115
+ :host => 'localhost',
116
+ :port => '3000',
117
+ :path => 'bar',
118
+ :search => 'baz=woo'
119
+ } ],
120
+
121
+ [ 'foo.com?baz=woo', {
122
+ :host => 'foo.com',
123
+ :search => 'baz=woo'
124
+ } ],
125
+
126
+ [ 'www.foo.com?baz=woo', {
127
+ :subdomain => 'www',
128
+ :host => 'foo.com',
129
+ :search => 'baz=woo'
130
+ } ],
131
+
132
+ [ 'http://foo.com?baz=woo', {
133
+ :scheme => 'http',
134
+ :host => 'foo.com',
135
+ :search => 'baz=woo'
136
+ } ],
137
+
138
+ [ 'http://www.foo.com?baz=woo', {
139
+ :scheme => 'http',
140
+ :subdomain => 'www',
141
+ :host => 'foo.com',
142
+ :search => 'baz=woo'
143
+ } ],
144
+
145
+ [ 'http://foo.com/bar?baz=woo', {
146
+ :scheme => 'http',
147
+ :host => 'foo.com',
148
+ :path => 'bar',
149
+ :search => 'baz=woo'
150
+ } ],
151
+
152
+ [ 'http://www.foo.com/bar?baz=woot', {
153
+ :scheme => 'http',
154
+ :subdomain => 'www',
155
+ :host => 'foo.com',
156
+ :path => 'bar',
157
+ :search => 'baz=woot'
158
+ } ],
159
+
160
+ [ 'http://localhost:3000?baz=woo', {
161
+ :scheme => 'http',
162
+ :host => 'localhost',
163
+ :port => '3000',
164
+ :search => 'baz=woo'
165
+ } ],
166
+
167
+ [ 'http://foo.com:8080?baz=woooo', {
168
+ :scheme => 'http',
169
+ :host => 'foo.com',
170
+ :port => '8080',
171
+ :search => 'baz=woooo'
172
+ } ],
173
+
174
+ [ 'http://foo.com:8080/bar?baz=woo', {
175
+ :scheme => 'http',
176
+ :host => 'foo.com',
177
+ :port => '8080',
178
+ :path => 'bar',
179
+ :search => 'baz=woo'
180
+ } ]
181
+ ].each do |string, parts|
182
+ it "should correctly parse the url parts for #{string}" do
183
+ parser.parse( string ).resolve.should eql( parts )
184
+ end
185
+ it "should correctly assemble the url parts to #{string}" do
186
+ assembler.assemble( parts ).should eql( string )
187
+ end
188
+ end
189
+ end
@@ -33,4 +33,17 @@ describe Earl::URL do
33
33
  url.subdomain = nil
34
34
  url.to_s.should eq( 'bar.com' )
35
35
  end
36
+
37
+ it 'should be able to change the host' do
38
+ url = Earl::URL.new 'www.foo.com'
39
+ url.host = 'foo.edu'
40
+ url.to_s.should eq( 'www.foo.edu' )
41
+ end
42
+ it 'should not be able to add a host' do
43
+ expect { url = Earl::URL.new 'http://' }.to raise_error( Earl::InvalidURLError )
44
+ end
45
+ it 'should not be able to remove a host' do
46
+ url = Earl::URL.new 'http://foo.com'
47
+ expect { url.host = nil }.to raise_error( Earl::InvalidURLError )
48
+ end
36
49
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: earl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,24 +9,30 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-13 00:00:00.000000000 Z
12
+ date: 2012-04-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: rspec
16
- requirement: !ruby/object:Gem::Requirement
15
+ name: treetop
16
+ requirement: &705960 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
20
20
  - !ruby/object:Gem::Version
21
- version: 2.9.0
22
- type: :development
21
+ version: 1.4.10
22
+ type: :runtime
23
23
  prerelease: false
24
- version_requirements: !ruby/object:Gem::Requirement
24
+ version_requirements: *705960
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ requirement: &705680 !ruby/object:Gem::Requirement
25
28
  none: false
26
29
  requirements:
27
30
  - - ! '>='
28
31
  - !ruby/object:Gem::Version
29
32
  version: 2.9.0
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *705680
30
36
  description: What URI wishes it could look like
31
37
  email:
32
38
  - jeremy.ruppel@gmail.com
@@ -42,12 +48,17 @@ files:
42
48
  - Rakefile
43
49
  - earl.gemspec
44
50
  - lib/earl.rb
51
+ - lib/earl/hash_inquirer.rb
45
52
  - lib/earl/string_inquirer.rb
46
53
  - lib/earl/url.rb
54
+ - lib/earl/url_assembler.rb
55
+ - lib/earl/url_parser.tt
47
56
  - lib/earl/version.rb
57
+ - spec/earl/hash_inquirer_spec.rb
48
58
  - spec/earl/parse_spec.rb
49
59
  - spec/earl/parts_spec.rb
50
60
  - spec/earl/string_inquirer_spec.rb
61
+ - spec/earl/url_parser_spec.rb
51
62
  - spec/earl/url_spec.rb
52
63
  - spec/spec_helper.rb
53
64
  homepage: https://github.com/remind101/earl
@@ -70,13 +81,15 @@ required_rubygems_version: !ruby/object:Gem::Requirement
70
81
  version: '0'
71
82
  requirements: []
72
83
  rubyforge_project:
73
- rubygems_version: 1.8.19
84
+ rubygems_version: 1.8.15
74
85
  signing_key:
75
86
  specification_version: 3
76
87
  summary: What URI wishes it could look like
77
88
  test_files:
89
+ - spec/earl/hash_inquirer_spec.rb
78
90
  - spec/earl/parse_spec.rb
79
91
  - spec/earl/parts_spec.rb
80
92
  - spec/earl/string_inquirer_spec.rb
93
+ - spec/earl/url_parser_spec.rb
81
94
  - spec/earl/url_spec.rb
82
95
  - spec/spec_helper.rb