furi 0.0.2 → 0.2.3

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.
@@ -0,0 +1,57 @@
1
+ module Furi
2
+ class QueryToken
3
+ attr_reader :name, :value
4
+
5
+ def self.parse(token)
6
+ case token
7
+ when QueryToken
8
+ token
9
+ when String
10
+ key, value = token.split('=', 2).map do |s|
11
+ ::URI.decode_www_form_component(s)
12
+ end
13
+ key ||= ""
14
+ new(key, value)
15
+ when Array
16
+ QueryToken.new(*token)
17
+ else
18
+ raise_parse_error(token)
19
+ end
20
+ end
21
+
22
+ def self.raise_parse_error(token)
23
+ raise QueryParseError, "Can not parse query token #{token.inspect}"
24
+ end
25
+
26
+ def initialize(name, value)
27
+ @name = name
28
+ @value = value
29
+ end
30
+
31
+ def to_a
32
+ [name, value]
33
+ end
34
+
35
+ def ==(other)
36
+ other = self.class.parse(other)
37
+ return false unless other
38
+ to_s == other.to_s
39
+ end
40
+
41
+ def to_s
42
+ encoded_key = ::URI.encode_www_form_component(name.to_s)
43
+
44
+ !value.nil? ?
45
+ "#{encoded_key}=#{::URI.encode_www_form_component(value.to_s)}" :
46
+ encoded_key
47
+ end
48
+
49
+ def as_json(options = nil)
50
+ to_a
51
+ end
52
+
53
+ def inspect
54
+ [name, value].join('=')
55
+ end
56
+ end
57
+ end
data/lib/furi/uri.rb ADDED
@@ -0,0 +1,595 @@
1
+ module Furi
2
+ class Uri
3
+
4
+ attr_reader(*Furi::ESSENTIAL_PARTS)
5
+
6
+ Furi::ALIASES.each do |origin, aliases|
7
+ aliases.each do |aliaz|
8
+ define_method(aliaz) do
9
+ self[origin]
10
+ end
11
+
12
+ define_method(:"#{aliaz}=") do |arg|
13
+ self[origin] = arg
14
+ end
15
+ end
16
+ end
17
+
18
+ def initialize(argument)
19
+ @query_tokens = []
20
+ case argument
21
+ when String
22
+ parse_uri_string(argument)
23
+ when Hash
24
+ replace(argument)
25
+ when ::URI::Generic
26
+ parse_uri_string(argument.to_s)
27
+ else
28
+ raise ParseError, "wrong Uri argument"
29
+ end
30
+ end
31
+
32
+ def replace(parts)
33
+ if parts
34
+ parts.each do |part, value|
35
+ self[part] = value
36
+ end
37
+ end
38
+ self
39
+ end
40
+
41
+ def update(parts)
42
+ return self unless parts
43
+ parts.each do |part, value|
44
+ case part.to_sym
45
+ when :query, :query_tokens, :query_string
46
+ merge_query(value)
47
+ else
48
+ self[part] = value
49
+ end
50
+ end
51
+ self
52
+ end
53
+
54
+ def defaults(parts)
55
+ parts.each do |part, value|
56
+ case part.to_sym
57
+ when :query, :query_tokens
58
+ Furi.parse_query(value).each do |key, default_value|
59
+ unless query.key?(key)
60
+ query[key] = default_value
61
+ end
62
+ end
63
+ else
64
+ unless self[part]
65
+ self[part] = value
66
+ end
67
+ end
68
+ end
69
+ self
70
+ end
71
+
72
+ def merge_query(query)
73
+ case query
74
+ when Hash
75
+ self.query = self.query.merge(Furi::Utils.stringify_keys(query))
76
+ when String, Array
77
+ self.query_tokens += Furi.query_tokens(query)
78
+ when nil
79
+ else
80
+ raise QueryParseError, "#{query.inspect} can not be merged"
81
+ end
82
+ end
83
+
84
+ def userinfo
85
+ if username
86
+ [username, password].compact.join(":")
87
+ elsif password
88
+ raise Furi::FormattingError, "can not build URI with password but without username"
89
+ else
90
+ nil
91
+ end
92
+ end
93
+
94
+ def host=(host)
95
+ @host = case host
96
+ when Array
97
+ join_domain(host)
98
+ when "", nil
99
+ nil
100
+ else
101
+ host.to_s.downcase
102
+ end
103
+ end
104
+
105
+ def domainzone
106
+ parsed_host.last
107
+ end
108
+
109
+ def domainzone=(new_zone)
110
+ self.host = [subdomain, domainname, new_zone]
111
+ end
112
+
113
+ def domainname
114
+ parsed_host[1]
115
+ end
116
+
117
+ def domainname=(new_domainname)
118
+ self.domain = join_domain([subdomain, new_domainname, domainzone])
119
+ end
120
+
121
+ def domain
122
+ join_domain(parsed_host[1..2].flatten)
123
+ end
124
+
125
+ def domain=(new_domain)
126
+ self.host= [subdomain, new_domain]
127
+ end
128
+
129
+ def subdomain
130
+ parsed_host.first
131
+ end
132
+
133
+ def subdomain=(new_subdomain)
134
+ self.host = [new_subdomain, domain]
135
+ end
136
+
137
+ def hostinfo
138
+ return host unless custom_port?
139
+ if port && !host
140
+ raise Furi::FormattingError, "can not build URI with port but without host"
141
+ end
142
+ [host, port].join(":")
143
+ end
144
+
145
+ def hostinfo=(string)
146
+ if string.match(%r{\A\[.+\]\z}) #ipv6 host
147
+ self.host = string
148
+ else
149
+ if match = string.match(/\A(.+):(.*)\z/)
150
+ self.host, self.port = match.captures
151
+ else
152
+ self.host = string
153
+ self.port = nil
154
+ end
155
+ end
156
+ end
157
+
158
+ def authority
159
+ return hostinfo unless userinfo
160
+ [userinfo, hostinfo].join("@")
161
+ end
162
+
163
+ def authority=(string)
164
+ if string.include?("@")
165
+ userinfo, string = string.split("@", 2)
166
+ self.userinfo = userinfo
167
+ else
168
+ self.userinfo = nil
169
+ end
170
+ self.hostinfo = string
171
+ end
172
+
173
+ def to_s
174
+ result = []
175
+ result << location
176
+ result << (host || mailto? ? path : path!)
177
+ if query_tokens.any?
178
+ result << "?" << query_string
179
+ end
180
+ if anchor
181
+ result << encoded_anchor
182
+ end
183
+ result.join
184
+ end
185
+
186
+ def location
187
+ if protocol
188
+ if !host && !mailto?
189
+ raise Furi::FormattingError, "can not build URI with protocol but without host"
190
+ end
191
+ [
192
+ protocol.empty? ? "" : "#{protocol}:", authority
193
+ ].join(mailto? ? "" : "//")
194
+ else
195
+ authority
196
+ end
197
+ end
198
+
199
+ def location=(string)
200
+ string ||= ""
201
+ string = string.gsub(%r(/\Z), '')
202
+ self.protocol = nil
203
+ string = parse_protocol(string)
204
+ self.authority = string
205
+ end
206
+
207
+ def request
208
+ return nil if !path && query_tokens.empty?
209
+ result = []
210
+ result << path!
211
+ result << "?" << query_string if query_tokens.any?
212
+ result.join
213
+ end
214
+
215
+ def request!
216
+ request || path!
217
+ end
218
+
219
+ def request=(string)
220
+ string = parse_anchor_and_query(string)
221
+ self.path = string
222
+ end
223
+
224
+ def home_page?
225
+ path! == Furi::ROOT || path! == "/index.html"
226
+ end
227
+
228
+ def query
229
+ return @query if query_level?
230
+ @query = Furi.parse_query(query_tokens)
231
+ end
232
+
233
+
234
+ def query=(value)
235
+ case value
236
+ when true
237
+ # Assuming that current query needs to be parsed to Hash
238
+ query
239
+ when String, Array
240
+ self.query_tokens = value
241
+ @query = nil
242
+ when Hash
243
+ self.query_tokens = value
244
+ @query = value
245
+ when nil
246
+ else
247
+ raise QueryParseError, 'Query can only be Hash or String'
248
+ end
249
+ end
250
+
251
+ def port=(port)
252
+ @port = case port
253
+ when String
254
+ if port.empty?
255
+ nil
256
+ else
257
+ unless port =~ /\A\s*\d+\s*\z/
258
+ raise ArgumentError, "port should be an Integer >= 0"
259
+ end
260
+ port.to_i
261
+ end
262
+ when Integer
263
+ if port < 0
264
+ raise ArgumentError, "port should be an Integer >= 0"
265
+ end
266
+ port
267
+ when nil
268
+ nil
269
+ else
270
+ raise ArgumentError, "can not parse port: #{port.inspect}"
271
+ end
272
+ @port
273
+ end
274
+
275
+ def query_tokens=(tokens)
276
+ @query = nil
277
+ @query_tokens = Furi.query_tokens(tokens)
278
+ end
279
+
280
+ def username=(username)
281
+ @username = username.nil? ? nil : username.to_s
282
+ end
283
+
284
+ def password=(password)
285
+ @password = password.nil? ? nil : password.to_s
286
+ end
287
+
288
+ def userinfo=(userinfo)
289
+ username, password = (userinfo || "").split(":", 2)
290
+ self.username = username
291
+ self.password = password
292
+ end
293
+
294
+ def path=(path)
295
+ @path = path.to_s
296
+ if !@path.empty? && !@path.start_with?("/")
297
+ @path = "/" + @path
298
+ end
299
+ end
300
+
301
+ def protocol=(protocol)
302
+ @protocol = protocol ? protocol.gsub(%r{:?/?/?\Z}, "").downcase : nil
303
+ end
304
+
305
+ def protocol!
306
+ protocol || default_protocol_for_port || 'http' # Web Rules Them All!
307
+ end
308
+
309
+ def directory
310
+ path_tokens[0..-2].join("/")
311
+ end
312
+
313
+ def directory=(string)
314
+ string ||= "/"
315
+ if file && string !~ %r{/\z}
316
+ string += '/'
317
+ end
318
+ self.path = string + file.to_s
319
+ end
320
+
321
+ def extension
322
+ return nil unless file
323
+ file_tokens.size > 1 ? file_tokens.last : nil
324
+ end
325
+
326
+ def extension=(string)
327
+ tokens = file_tokens
328
+ case tokens.size
329
+ when 0
330
+ raise Furi::FormattingError, "can not assign extension when there is no file"
331
+ when 1
332
+ tokens.push(string)
333
+ else
334
+ if string
335
+ tokens[-1] = string
336
+ else
337
+ tokens.pop
338
+ end
339
+ end
340
+ self.file = tokens.join(".")
341
+ end
342
+
343
+ def file=(name)
344
+ unless name
345
+ return unless path
346
+ else
347
+ name = name.gsub(%r{\A/}, "")
348
+ end
349
+
350
+ self.path = path_tokens.tap do |p|
351
+ filename_index = [p.size-1, 0].max
352
+ p[filename_index] = name
353
+ end.join("/")
354
+ end
355
+
356
+ def path_tokens
357
+ return [] unless path
358
+ path.split("/", -1)
359
+ end
360
+
361
+
362
+ def query_string
363
+ if query_level?
364
+ Furi.serialize(query)
365
+ else
366
+ query_tokens.any? ? query_tokens.join("&") : nil
367
+ end
368
+ end
369
+
370
+ def query_string!
371
+ query_string || ""
372
+ end
373
+
374
+ def query_string=(string)
375
+ self.query_tokens = string.to_s
376
+ end
377
+
378
+ def port!
379
+ port || default_port
380
+ end
381
+
382
+ def default_port
383
+ Furi::PROTOCOLS.fetch(protocol, {})[:port]
384
+ end
385
+
386
+ def ssl?
387
+ !!(Furi::PROTOCOLS.fetch(protocol, {})[:ssl])
388
+ end
389
+
390
+ def ssl
391
+ ssl?
392
+ end
393
+
394
+ def ssl=(ssl)
395
+ self.protocol = find_protocol_for_ssl(ssl)
396
+ end
397
+
398
+ def file
399
+ result = path_tokens.last
400
+ result == "" ? nil : result
401
+ end
402
+
403
+ def file!
404
+ file || ''
405
+ end
406
+
407
+ def default_web_port?
408
+ Furi::WEB_PROTOCOL.any? do |web_protocol|
409
+ Furi::PROTOCOLS[web_protocol][:port] == port!
410
+ end
411
+ end
412
+
413
+ def web_protocol?
414
+ Furi::WEB_PROTOCOL.include?(protocol)
415
+ end
416
+
417
+ def abstract_protocol?
418
+ protocol == ""
419
+ end
420
+
421
+ def resource
422
+ return nil unless request
423
+ request + encoded_anchor
424
+ end
425
+
426
+ def resource=(value)
427
+ self.anchor = nil
428
+ self.query_tokens = []
429
+ self.path = nil
430
+ value = parse_anchor_and_query(value)
431
+ self.path = value
432
+ end
433
+
434
+ def path!
435
+ path || Furi::ROOT
436
+ end
437
+
438
+ def resource!
439
+ resource || request!
440
+ end
441
+
442
+ def host!
443
+ host || ""
444
+ end
445
+
446
+ def ==(other)
447
+ to_s == other.to_s
448
+ end
449
+
450
+ def inspect
451
+ "#<#{self.class} #{to_s.inspect}>"
452
+ end
453
+
454
+ def anchor=(string)
455
+ string = string.to_s
456
+ @anchor = string.empty? ? nil : string
457
+ end
458
+
459
+ def [](part)
460
+ send(part)
461
+ end
462
+
463
+ def []=(part, value)
464
+ send(:"#{part}=", value)
465
+ end
466
+
467
+ def rfc?
468
+ rfc3986?
469
+ end
470
+
471
+ def rfc3986?
472
+ uri = to_s
473
+ !!(uri.match(URI::RFC3986_Parser::RFC3986_URI) ||
474
+ uri.match(URI::RFC3986_Parser::RFC3986_relative_ref))
475
+ end
476
+
477
+ def email=(email)
478
+ self.protocol ||= "mailto"
479
+ self.authority = email
480
+ end
481
+
482
+ def email
483
+ authority
484
+ end
485
+
486
+ def custom_port?
487
+ port && port != default_port
488
+ end
489
+
490
+ def mailto?
491
+ protocol == "mailto"
492
+ end
493
+
494
+ protected
495
+
496
+ def file_tokens
497
+ file ? file.split('.') : []
498
+ end
499
+
500
+ def query_level?
501
+ !!@query
502
+ end
503
+
504
+ def parse_uri_string(string)
505
+ if string.empty?
506
+ raise Furi::FormattingError, "can not be an empty string"
507
+ end
508
+ string = parse_anchor_and_query(string)
509
+
510
+ string = parse_protocol(string)
511
+
512
+ if string.include?("/")
513
+ string, path = string.split("/", 2)
514
+ self.path = "/" + path
515
+ end
516
+
517
+ self.authority = string
518
+ end
519
+
520
+ def find_protocol_for_ssl(ssl)
521
+ if Furi::SSL_MAPPING.key?(protocol)
522
+ ssl ? Furi::SSL_MAPPING[protocol] : protocol
523
+ elsif Furi::SSL_MAPPING.values.include?(protocol)
524
+ ssl ? protocol : Furi::SSL_MAPPING.invert[protocol]
525
+ else
526
+ raise ArgumentError, "Can not specify SSL for #{protocol.inspect} protocol"
527
+ end
528
+ end
529
+
530
+ def join_domain(tokens)
531
+ tokens = tokens.compact
532
+ tokens.any? ? tokens.join(".") : nil
533
+ end
534
+
535
+ def parse_anchor_and_query(string)
536
+ string ||= ''
537
+ string, *anchor = string.split("#")
538
+ self.anchor = ::URI::DEFAULT_PARSER.unescape(anchor.join("#"))
539
+ if string && string.include?("?")
540
+ string, query_string = string.split("?", 2)
541
+ self.query_tokens = query_string
542
+ end
543
+ string
544
+ end
545
+
546
+ def join(uri)
547
+ Uri.new(::URI.join(to_s, uri.to_s))
548
+ end
549
+
550
+ def parse_protocol(string)
551
+ if string.include?("://") || string.start_with?("mailto:")
552
+ protocol, string = string.split(":", 2)
553
+ self.protocol = protocol
554
+ end
555
+ if string.start_with?("//")
556
+ self.protocol ||= ''
557
+ string = string[2..-1]
558
+ end
559
+ string
560
+ end
561
+
562
+ def parsed_host
563
+ return @parsed_host if @parsed_host
564
+ tokens = host_tokens
565
+ zone = []
566
+ subdomain = []
567
+ while tokens.any? && tokens.last.size <= 3 && tokens.size >= 2
568
+ zone.unshift tokens.pop
569
+ end
570
+ while tokens.size > 1
571
+ subdomain << tokens.shift
572
+ end
573
+ domainname = tokens.first
574
+ @parsed_host = [join_domain(subdomain), domainname, join_domain(zone)]
575
+ end
576
+
577
+ def host_tokens
578
+ host.split(".")
579
+ end
580
+
581
+ def default_protocol_for_port
582
+ return nil unless port
583
+ PROTOCOLS.each do |protocol, data|
584
+ if data[:port] == port
585
+ return protocol
586
+ end
587
+ end
588
+ end
589
+
590
+ def encoded_anchor
591
+ return "" unless anchor
592
+ "#" + ::URI::DEFAULT_PARSER.escape(anchor)
593
+ end
594
+ end
595
+ end