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