furi 0.0.2 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 208063d16947ce85a4d80a12bd3834cb26c0f6ab
4
- data.tar.gz: ab3eff6b787f0df3e04781908477bb69d3924333
3
+ metadata.gz: 62a4088dd18cb4ac9857d1512d78497ea28edd82
4
+ data.tar.gz: 3603fa3688b41b7b2ba700676db29f930bb11bf3
5
5
  SHA512:
6
- metadata.gz: 500c3e4bb3ed0866c0cef1ca68794d9d82e9d2b679bb1590f69e14428bb1020a64d5c357c166e25b0c522902b670e5138269e649f395455f48acc4bca50785eb
7
- data.tar.gz: bae87908fe1a04c26d1073d569199adda1645bc25f561ec0aa9b2e05943e55d0993223c3d329335c07afcac8c814e87c2a738573a2837be2996cb38339e3078f
6
+ metadata.gz: 762e38c65d27a36323bf0ed410b25633e95788ce10a8a97163195918a47e038610defe4fe8f7f5b635d554c42b09cc3c5d4fbc1974dee9ec5d4e645d646923c1
7
+ data.tar.gz: 59f5e066fb0f09bcac768b99b11589d4257f391352e27d4d6874c2ae1d2d783f8bc56fde491906669b5aed5d22f16c17a8f424cbab486bd2b3123243c7b38873
data/Rakefile CHANGED
@@ -1,2 +1,13 @@
1
1
  require "bundler/gem_tasks"
2
2
 
3
+
4
+ begin
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new(:spec)
7
+ rescue LoadError
8
+ end
9
+
10
+ task :default => :spec do
11
+
12
+ end
13
+
@@ -4,32 +4,44 @@ require "uri"
4
4
  module Furi
5
5
 
6
6
  PARTS = [
7
- :anchor, :protocol, :query_string,
8
- :path, :hostname, :port, :username, :password
7
+ :anchor, :protocol, :query_tokens,
8
+ :path, :host, :port, :username, :password
9
9
  ]
10
10
  ALIASES = {
11
- protocol: [:schema],
11
+ protocol: [:schema, :scheme],
12
12
  anchor: [:fragment],
13
+ host: [:hostname],
14
+ username: [:user],
13
15
  }
14
16
 
15
- DELEGATES = [:port!]
16
-
17
- PORT_MAPPING = {
18
- "http" => 80,
19
- "https" => 443,
20
- "ftp" => 21,
21
- "tftp" => 69,
22
- "sftp" => 22,
23
- "ssh" => 22,
24
- "svn+ssh" => 22,
25
- "telnet" => 23,
26
- "nntp" => 119,
27
- "gopher" => 70,
28
- "wais" => 210,
29
- "ldap" => 389,
30
- "prospero" => 1525
17
+ DELEGATES = [:port!, :host!]
18
+
19
+ PROTOCOLS = {
20
+ "http" => {port: 80, ssl: false},
21
+ "https" => {port: 443, ssl: true},
22
+ "ftp" => {port: 21},
23
+ "tftp" => {port: 69},
24
+ "sftp" => {port: 22},
25
+ "ssh" => {port: 22, ssl: true},
26
+ "svn+ssh" => {port: 22, ssl: true},
27
+ "telnet" => {port: 23},
28
+ "nntp" => {port: 119},
29
+ "gopher" => {port: 70},
30
+ "wais" => {port: 210},
31
+ "ldap" => {port: 389},
32
+ "prospero" => {port: 1525},
31
33
  }
32
34
 
35
+ SSL_MAPPING = {
36
+ 'http' => 'https',
37
+ 'ftp' => 'sftp',
38
+ 'svn' => 'svn+ssh',
39
+ }
40
+
41
+ WEB_PROTOCOL = ['http', 'https']
42
+
43
+ ROOT = '/'
44
+
33
45
  class Expressions
34
46
  attr_accessor :protocol
35
47
 
@@ -62,6 +74,10 @@ module Furi
62
74
  parse(string).update(parts).to_s
63
75
  end
64
76
 
77
+ def self.merge(string, parts)
78
+ parse(string).merge(parts).to_s
79
+ end
80
+
65
81
  def self.serialize_tokens(query, namespace = nil)
66
82
  case query
67
83
  when Hash
@@ -107,7 +123,8 @@ module Furi
107
123
  end
108
124
 
109
125
  def self.query_tokens(query)
110
- if query.is_a?(Array)
126
+ case query
127
+ when Enumerable, Enumerator
111
128
  query.map do |token|
112
129
  case token
113
130
  when QueryToken
@@ -120,10 +137,14 @@ module Furi
120
137
  raise ArgumentError, "Can not parse query token #{token.inspect}"
121
138
  end
122
139
  end
123
- else
124
- (query || '').split(/[&;] */n).map do |p|
140
+ when nil, ''
141
+ []
142
+ when String
143
+ query.gsub(/\A\?/, '').split(/[&;] */n).map do |p|
125
144
  QueryToken.parse(p)
126
145
  end
146
+ else
147
+ raise ArgumentError, "Can not parse #{query.inspect} query tokens"
127
148
  end
128
149
  end
129
150
 
@@ -166,8 +187,8 @@ module Furi
166
187
  return params
167
188
  end
168
189
 
169
- def self.serialize(string, namespace = nil)
170
- serialize_tokens(string, namespace).join("&")
190
+ def self.serialize(query, namespace = nil)
191
+ serialize_tokens(query, namespace).join("&")
171
192
  end
172
193
 
173
194
  class QueryToken
@@ -187,6 +208,10 @@ module Furi
187
208
  [name, value]
188
209
  end
189
210
 
211
+ def ==(other)
212
+ to_s == other.to_s
213
+ end
214
+
190
215
  def to_s
191
216
  "#{::URI.encode_www_form_component(name.to_s)}=#{::URI.encode_www_form_component(value.to_s)}"
192
217
  end
@@ -213,6 +238,7 @@ module Furi
213
238
  end
214
239
 
215
240
  def initialize(argument)
241
+ @query_tokens = []
216
242
  case argument
217
243
  when String
218
244
  parse_uri_string(argument)
@@ -230,7 +256,25 @@ module Furi
230
256
 
231
257
  def merge(parts)
232
258
  parts.each do |part, value|
233
-
259
+ case part.to_sym
260
+ when :query
261
+ merge_query(value)
262
+ else
263
+ send(:"#{part}=", value)
264
+ end
265
+ end
266
+ self
267
+ end
268
+
269
+ def merge_query(query)
270
+ case query
271
+ when Hash
272
+ self.query = self.query.merge(Furi::Utils.stringify_keys(query))
273
+ when String, Array
274
+ self.query_tokens += Furi.query_tokens(query)
275
+ when nil
276
+ else
277
+ raise ArgumentError, "#{query.inspect} can not be merged"
234
278
  end
235
279
  end
236
280
 
@@ -243,24 +287,9 @@ module Furi
243
287
  nil
244
288
  end
245
289
  end
246
-
247
- def host
248
- if hostname
249
- [hostname, explicit_port].compact.join(":")
250
- elsif port
251
- raise FormattingError, "can not build URI with port but without hostname"
252
- else
253
- nil
254
- end
255
- end
256
-
257
- def host=(string)
258
- @port = nil
259
- if string.include?(":")
260
- string, @port = string.split(":", 2)
261
- @port = @port.to_i
262
- end
263
- @hostname = string.empty? ? nil : string
290
+
291
+ def host=(host)
292
+ @host = host
264
293
  end
265
294
 
266
295
  def to_s
@@ -272,8 +301,9 @@ module Furi
272
301
  result << userinfo
273
302
  end
274
303
  result << host if host
275
- result << path
276
- if query_string
304
+ result << ":" << port if explicit_port
305
+ result << (host ? path : path!)
306
+ if query_tokens.any?
277
307
  result << "?" << query_string
278
308
  end
279
309
  if anchor
@@ -282,45 +312,66 @@ module Furi
282
312
  result.join
283
313
  end
284
314
 
315
+
316
+ def request
317
+ result = []
318
+ result << path!
319
+ result << "?" << query_string if query_tokens.any?
320
+ result.join
321
+ end
322
+
323
+ def request_uri
324
+ request
325
+ end
326
+
285
327
  def query
286
328
  return @query if query_level?
287
- @query = Furi.parse_nested_query(@query_string)
329
+ @query = Furi.parse_nested_query(query_tokens)
288
330
  end
289
331
 
290
332
 
291
333
  def query=(value)
292
334
  @query = nil
335
+ @query_tokens = []
293
336
  case value
294
- when String
295
- @query_string = value
296
- when Array
297
- @query = Furi.query_tokens(value)
337
+ when String, Array
338
+ @query_tokens = Furi.query_tokens(value)
298
339
  when Hash
299
340
  @query = value
341
+ @query_tokens = Furi.serialize_tokens(value)
300
342
  when nil
301
343
  else
302
344
  raise ArgumentError, 'Query can only be Hash or String'
303
345
  end
304
346
  end
305
347
 
306
- def hostname=(hostname)
307
- @hostname = hostname
308
- end
309
-
310
348
  def port=(port)
311
- @port = port.to_i
312
- if @port == 0
313
- raise ArgumentError, "port should be an Integer > 0"
349
+ if port != nil
350
+ @port = port.to_i
351
+ if @port == 0
352
+ raise ArgumentError, "port should be an Integer > 0"
353
+ end
354
+ else
355
+ @port = nil
314
356
  end
315
357
  @port
316
358
  end
317
359
 
360
+ def query_tokens=(tokens)
361
+ @query = nil
362
+ @query_tokens = tokens
363
+ end
364
+
318
365
  def username=(username)
319
- @username = username
366
+ @username = username.nil? ? nil : username.to_s
320
367
  end
321
368
 
322
369
  def password=(password)
323
- @password = password
370
+ @password = password.nil? ? nil : password.to_s
371
+ end
372
+
373
+ def path=(path)
374
+ @path = path.to_s
324
375
  end
325
376
 
326
377
  def protocol=(protocol)
@@ -328,8 +379,11 @@ module Furi
328
379
  end
329
380
 
330
381
  def query_string
331
- return @query_string unless query_level?
332
- Furi.serialize(@query)
382
+ if query_level?
383
+ Furi.serialize(@query)
384
+ else
385
+ query_tokens.join("&")
386
+ end
333
387
  end
334
388
 
335
389
  def expressions
@@ -341,7 +395,49 @@ module Furi
341
395
  end
342
396
 
343
397
  def default_port
344
- protocol ? PORT_MAPPING[protocol] : nil
398
+ protocol && PROTOCOLS[protocol] ? PROTOCOLS[protocol][:port] : nil
399
+ end
400
+
401
+ def ssl?
402
+ !!(protocol && PROTOCOLS[protocol][:ssl])
403
+ end
404
+
405
+ def ssl
406
+ ssl?
407
+ end
408
+
409
+ def ssl=(ssl)
410
+ self.protocol = find_protocol_for_ssl(ssl)
411
+ end
412
+
413
+ def filename
414
+ path.split("/").last
415
+ end
416
+
417
+ def default_web_port?
418
+ WEB_PROTOCOL.any? do |web_protocol|
419
+ PROTOCOLS[web_protocol][:port] == port!
420
+ end
421
+ end
422
+
423
+ def web_protocol?
424
+ WEB_PROTOCOL.include?(protocol)
425
+ end
426
+
427
+ def resource
428
+ [request, anchor].compact.join("#")
429
+ end
430
+
431
+ def path!
432
+ path || ROOT
433
+ end
434
+
435
+ def host!
436
+ host || ""
437
+ end
438
+
439
+ def ==(other)
440
+ to_s == other.to_s
345
441
  end
346
442
 
347
443
  protected
@@ -359,7 +455,7 @@ module Furi
359
455
  @anchor = @anchor.empty? ? nil : @anchor.join("#")
360
456
  if string.include?("?")
361
457
  string, query_string = string.split("?", 2)
362
- @query_string = query_string
458
+ self.query_tokens = Furi.query_tokens(query_string)
363
459
  end
364
460
 
365
461
  if string.include?("://")
@@ -383,11 +479,35 @@ module Furi
383
479
  userinfo, string = string.split("@", 2)
384
480
  @username, @password = userinfo.split(":", 2)
385
481
  end
386
- self.host = string
482
+ host, port = string.split(":", 2)
483
+ self.host = host if host
484
+ self.port = port if port
485
+ end
486
+
487
+ def find_protocol_for_ssl(ssl)
488
+ if SSL_MAPPING.key?(protocol)
489
+ ssl ? SSL_MAPPING[protocol] : protocol
490
+ elsif SSL_MAPPING.values.include?(protocol)
491
+ ssl ? protocol : SSL_MAPPING.invert[protocol]
492
+ else
493
+ raise ArgumentError, "Can not specify ssl for #{protocol.inspect} protocol"
494
+ end
387
495
  end
388
496
 
389
497
  end
390
498
 
391
499
  class FormattingError < StandardError
392
500
  end
501
+
502
+ class Utils
503
+ class << self
504
+ def stringify_keys(hash)
505
+ result = {}
506
+ hash.each_key do |key|
507
+ result[key.to_s] = hash[key]
508
+ end
509
+ result
510
+ end
511
+ end
512
+ end
393
513
  end
@@ -1,3 +1,3 @@
1
1
  module Furi
2
- VERSION = "0.0.2"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -69,10 +69,13 @@ describe Furi do
69
69
  expect("http://gusiev.com").to have_parts(
70
70
  protocol: 'http',
71
71
  hostname: 'gusiev.com',
72
- query_string: nil,
72
+ query_string: "",
73
73
  query: {},
74
74
  path: nil,
75
+ path!: '/',
75
76
  port: nil,
77
+ request: '/',
78
+ resource: '/',
76
79
  )
77
80
  end
78
81
 
@@ -83,6 +86,8 @@ describe Furi do
83
86
  path: '/posts/index.html',
84
87
  port: nil,
85
88
  protocol: 'http',
89
+ resource: '/posts/index.html?a=b#zz',
90
+ request: '/posts/index.html?a=b',
86
91
  )
87
92
  end
88
93
 
@@ -100,7 +105,7 @@ describe Furi do
100
105
  username: 'user',
101
106
  password: 'pass',
102
107
  hostname: 'gusiev.com',
103
- query_string: nil,
108
+ query_string: "",
104
109
  anchor: nil,
105
110
  )
106
111
  end
@@ -110,7 +115,7 @@ describe Furi do
110
115
  username: 'user',
111
116
  password: nil,
112
117
  hostname: 'gusiev.com',
113
- query_string: nil,
118
+ query_string: "",
114
119
  anchor: nil,
115
120
  )
116
121
  end
@@ -130,14 +135,16 @@ describe Furi do
130
135
  userinfo: 'user:pass',
131
136
  protocol: 'http',
132
137
  port: 80,
133
- query_string: nil,
138
+ query_string: "",
134
139
  )
135
140
  end
136
141
  it "parses url with query" do
137
142
  expect("/index.html?a=b&c=d").to have_parts(
138
143
  host: nil,
144
+ host!: '',
139
145
  query_string: 'a=b&c=d',
140
- query: {'a' => 'b', 'c' => 'd'}
146
+ query: {'a' => 'b', 'c' => 'd'},
147
+ request: '/index.html?a=b&c=d',
141
148
  )
142
149
  end
143
150
 
@@ -148,6 +155,23 @@ describe Furi do
148
155
  "port!" => 80
149
156
  )
150
157
  end
158
+ it "parses nested query" do
159
+ expect("gusiev.com?a[]=1&a[]=2&b[c]=1&b[d]=2").to have_parts(
160
+ host: 'gusiev.com',
161
+ query: {"a" => ["1","2"], "b" => {"c" => "1", "d" => "2"}},
162
+ )
163
+ end
164
+
165
+ it "find out protocol security" do
166
+ expect("gusiev.com:443").to have_parts(
167
+ host: 'gusiev.com',
168
+ :"ssl" => false
169
+ )
170
+ expect("https://gusiev.com:443").to have_parts(
171
+ host: 'gusiev.com',
172
+ :"ssl" => true
173
+ )
174
+ end
151
175
  end
152
176
  describe ".update" do
153
177
 
@@ -160,6 +184,8 @@ describe Furi do
160
184
  expect(Furi.update("/index.html", hostname: 'gusiev.com')).to eq('gusiev.com/index.html')
161
185
  expect(Furi.update("http://www.gusiev.com/index.html", hostname: 'gusiev.com')).to eq('http://gusiev.com/index.html')
162
186
  expect(Furi.update("/index.html", hostname: 'gusiev.com')).to eq('gusiev.com/index.html')
187
+ expect(Furi.update("gusiev.com/index.html?a=b", hostname: nil)).to eq('/index.html?a=b')
188
+ expect(Furi.update("gusiev.com?a=b", hostname: nil)).to eq('/?a=b')
163
189
  end
164
190
 
165
191
  it "updates port" do
@@ -168,6 +194,13 @@ describe Furi do
168
194
  expect(Furi.update("gusiev.com:33/index.html", port: 80)).to eq('gusiev.com:80/index.html')
169
195
  expect(Furi.update("http://gusiev.com:33/index.html", port: 80)).to eq('http://gusiev.com/index.html')
170
196
  end
197
+
198
+ it "updates ssl" do
199
+ expect(Furi.update("http://gusiev.com", ssl: true)).to eq('https://gusiev.com')
200
+ expect(Furi.update("https://gusiev.com", ssl: true)).to eq('https://gusiev.com')
201
+ expect(Furi.update("https://gusiev.com", ssl: false)).to eq('http://gusiev.com')
202
+ expect(Furi.update("http://gusiev.com", ssl: false)).to eq('http://gusiev.com')
203
+ end
171
204
  it "updates protocol" do
172
205
  expect(Furi.update("http://gusiev.com", protocol: '')).to eq('//gusiev.com')
173
206
  expect(Furi.update("http://gusiev.com", protocol: nil)).to eq('gusiev.com')
@@ -187,9 +220,42 @@ describe Furi do
187
220
  expect(Furi.build(hostname: 'hello.com', port: 88)).to eq('hello.com:88')
188
221
  expect(Furi.build(schema: 'https', hostname: 'hello.com', port: 88)).to eq('https://hello.com:88')
189
222
  expect(Furi.build(schema: 'http', hostname: 'hello.com', port: 80)).to eq('http://hello.com')
223
+ expect(Furi.build(path: '/index.html', query: {a: 1, b: 2})).to eq('/index.html?a=1&b=2')
190
224
  end
191
225
  end
192
226
 
227
+ describe ".merge" do
228
+ it "should work" do
229
+ expect(Furi.merge("//gusiev.com", query: {a: 1})).to eq('//gusiev.com?a=1')
230
+ expect(Furi.merge("//gusiev.com?a=1", query: {b: 2})).to eq('//gusiev.com?a=1&b=2')
231
+ expect(Furi.merge("//gusiev.com?a=1", query: {a: 2})).to eq('//gusiev.com?a=2')
232
+ expect(Furi.merge("//gusiev.com?a=1", query: [['a', 2], ['b', 3]])).to eq('//gusiev.com?a=1&a=2&b=3')
233
+ expect(Furi.merge("//gusiev.com?a=1&b=2", query: '?a=3')).to eq('//gusiev.com?a=1&b=2&a=3')
234
+ end
235
+ end
236
+
237
+ describe "#==" do
238
+ it "should work" do
239
+ expect(Furi.parse('http://gusiev.com:80') == Furi.parse('http://gusiev.com')).to be_truthy
240
+ expect(Furi.parse('http://gusiev.com') == Furi.parse('https://gusiev.com')).to be_falsey
241
+ expect(Furi.parse('http://gusiev.com') == Furi.parse('http://gusiev.com')).to be_truthy
242
+ expect(Furi.parse('http://gusiev.com.ua') == Furi.parse('http://gusiev.com')).to be_falsey
243
+ expect(Furi.parse('http://gusiev.com?a=1&a=1') == Furi.parse('http://gusiev.com?a=1')).to be_falsey
244
+ end
245
+ end
246
+
247
+ describe "#clone" do
248
+ it "should work" do
249
+
250
+ uri = Furi.parse('http://gusiev.com')
251
+ expect(uri.clone == uri).to be_truthy
252
+ expect(uri.clone.merge_query([[:a, 1]]) == uri).to be_falsey
253
+ expect(Furi.parse('http://gusiev.com') == Furi.parse('https://gusiev.com')).to be_falsey
254
+ expect(Furi.parse('http://gusiev.com') == Furi.parse('http://gusiev.com')).to be_truthy
255
+ expect(Furi.parse('http://gusiev.com.ua') == Furi.parse('http://gusiev.com')).to be_falsey
256
+ expect(Furi.parse('http://gusiev.com?a=1&a=1') == Furi.parse('http://gusiev.com?a=1')).to be_falsey
257
+ end
258
+ end
193
259
 
194
260
  describe "serialize" do
195
261
  it "should work" do
@@ -302,7 +368,6 @@ describe Furi do
302
368
 
303
369
  lambda { Furi.parse_nested_query("x[y]=1&x[y][][w]=2") }.
304
370
  should raise_error(TypeError, "expected Array (got String) for param `y'")
305
-
306
371
  end
307
372
 
308
373
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: furi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bogdan Gusiev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-07-07 00:00:00.000000000 Z
11
+ date: 2015-07-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler