addressable 2.3.3 → 2.8.9

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.
@@ -1,6 +1,7 @@
1
- # encoding:utf-8
1
+ # frozen_string_literal: true
2
+
2
3
  #--
3
- # Copyright (C) 2006-2011 Bob Aman
4
+ # Copyright (C) Bob Aman
4
5
  #
5
6
  # Licensed under the Apache License, Version 2.0 (the "License");
6
7
  # you may not use this file except in compliance with the License.
@@ -1,6 +1,7 @@
1
- # encoding:utf-8
1
+ # frozen_string_literal: true
2
+
2
3
  #--
3
- # Copyright (C) 2006-2011 Bob Aman
4
+ # Copyright (C) Bob Aman
4
5
  #
5
6
  # Licensed under the Apache License, Version 2.0 (the "License");
6
7
  # you may not use this file except in compliance with the License.
@@ -35,7 +36,7 @@ module Addressable
35
36
  Addressable::URI::CharacterClasses::DIGIT + '_'
36
37
 
37
38
  var_char =
38
- "(?:(?:[#{variable_char_class}]|%[a-fA-F0-9][a-fA-F0-9])+)"
39
+ "(?>(?:[#{variable_char_class}]|%[a-fA-F0-9][a-fA-F0-9])+)"
39
40
  RESERVED =
40
41
  "(?:[#{anything}]|%[a-fA-F0-9][a-fA-F0-9])"
41
42
  UNRESERVED =
@@ -132,6 +133,7 @@ module Addressable
132
133
  self.template.variables
133
134
  end
134
135
  alias_method :keys, :variables
136
+ alias_method :names, :variables
135
137
 
136
138
  ##
137
139
  # @return [Array]
@@ -146,6 +148,64 @@ module Addressable
146
148
  end
147
149
  alias_method :captures, :values
148
150
 
151
+ ##
152
+ # Accesses captured values by name or by index.
153
+ #
154
+ # @param [String, Symbol, Fixnum] key
155
+ # Capture index or name. Note that when accessing by with index
156
+ # of 0, the full URI will be returned. The intention is to mimic
157
+ # the ::MatchData#[] behavior.
158
+ #
159
+ # @param [#to_int, nil] len
160
+ # If provided, an array of values will be returned with the given
161
+ # parameter used as length.
162
+ #
163
+ # @return [Array, String, nil]
164
+ # The captured value corresponding to the index or name. If the
165
+ # value was not provided or the key is unknown, nil will be
166
+ # returned.
167
+ #
168
+ # If the second parameter is provided, an array of that length will
169
+ # be returned instead.
170
+ def [](key, len = nil)
171
+ if len
172
+ to_a[key, len]
173
+ elsif String === key or Symbol === key
174
+ mapping[key.to_s]
175
+ else
176
+ to_a[key]
177
+ end
178
+ end
179
+
180
+ ##
181
+ # @return [Array]
182
+ # Array with the matched URI as first element followed by the captured
183
+ # values.
184
+ def to_a
185
+ [to_s, *values]
186
+ end
187
+
188
+ ##
189
+ # @return [String]
190
+ # The matched URI as String.
191
+ def to_s
192
+ uri.to_s
193
+ end
194
+ alias_method :string, :to_s
195
+
196
+ # Returns multiple captured values at once.
197
+ #
198
+ # @param [String, Symbol, Fixnum] *indexes
199
+ # Indices of the captures to be returned
200
+ #
201
+ # @return [Array]
202
+ # Values corresponding to given indices.
203
+ #
204
+ # @see Addressable::Template::MatchData#[]
205
+ def values_at(*indexes)
206
+ indexes.map { |i| self[i] }
207
+ end
208
+
149
209
  ##
150
210
  # Returns a <tt>String</tt> representation of the MatchData's state.
151
211
  #
@@ -154,6 +214,15 @@ module Addressable
154
214
  sprintf("#<%s:%#0x RESULT:%s>",
155
215
  self.class.to_s, self.object_id, self.mapping.inspect)
156
216
  end
217
+
218
+ ##
219
+ # Dummy method for code expecting a ::MatchData instance
220
+ #
221
+ # @return [String] An empty string.
222
+ def pre_match
223
+ ""
224
+ end
225
+ alias_method :post_match, :pre_match
157
226
  end
158
227
 
159
228
  ##
@@ -166,7 +235,18 @@ module Addressable
166
235
  if !pattern.respond_to?(:to_str)
167
236
  raise TypeError, "Can't convert #{pattern.class} into String."
168
237
  end
169
- @pattern = pattern.to_str.freeze
238
+ @pattern = pattern.to_str.dup.freeze
239
+ end
240
+
241
+ ##
242
+ # Freeze URI, initializing instance variables.
243
+ #
244
+ # @return [Addressable::URI] The frozen URI object.
245
+ def freeze
246
+ self.variables
247
+ self.variable_defaults
248
+ self.named_captures
249
+ super
170
250
  end
171
251
 
172
252
  ##
@@ -182,6 +262,26 @@ module Addressable
182
262
  self.class.to_s, self.object_id, self.pattern)
183
263
  end
184
264
 
265
+ ##
266
+ # Returns <code>true</code> if the Template objects are equal. This method
267
+ # does NOT normalize either Template before doing the comparison.
268
+ #
269
+ # @param [Object] template The Template to compare.
270
+ #
271
+ # @return [TrueClass, FalseClass]
272
+ # <code>true</code> if the Templates are equivalent, <code>false</code>
273
+ # otherwise.
274
+ def ==(template)
275
+ return false unless template.kind_of?(Template)
276
+ return self.pattern == template.pattern
277
+ end
278
+
279
+ ##
280
+ # Addressable::Template makes no distinction between `==` and `eql?`.
281
+ #
282
+ # @see #==
283
+ alias_method :eql?, :==
284
+
185
285
  ##
186
286
  # Extracts a mapping from the URI using a URI Template pattern.
187
287
  #
@@ -311,7 +411,7 @@ module Addressable
311
411
  # match.captures
312
412
  # #=> ["a", ["b", "c"]]
313
413
  def match(uri, processor=nil)
314
- uri = Addressable::URI.parse(uri)
414
+ uri = Addressable::URI.parse(uri) unless uri.is_a?(Addressable::URI)
315
415
  mapping = {}
316
416
 
317
417
  # First, we need to process the pattern, and extract the values.
@@ -329,6 +429,7 @@ module Addressable
329
429
  _, operator, varlist = *expansion.match(EXPRESSION)
330
430
  varlist.split(',').each do |varspec|
331
431
  _, name, modifier = *varspec.match(VARSPEC)
432
+ mapping[name] ||= nil
332
433
  case operator
333
434
  when nil, '+', '#', '/', '.'
334
435
  unparsed_value = unparsed_values[index]
@@ -337,12 +438,14 @@ module Addressable
337
438
  value = value.split(JOINERS[operator]) if value && modifier == '*'
338
439
  when ';', '?', '&'
339
440
  if modifier == '*'
340
- value = unparsed_values[index].split(JOINERS[operator])
341
- value = value.inject({}) do |acc, v|
342
- key, val = v.split('=')
343
- val = "" if val.nil?
344
- acc[key] = val
345
- acc
441
+ if unparsed_values[index]
442
+ value = unparsed_values[index].split(JOINERS[operator])
443
+ value = value.inject({}) do |acc, v|
444
+ key, val = v.split('=')
445
+ val = "" if val.nil?
446
+ acc[key] = val
447
+ acc
448
+ end
346
449
  end
347
450
  else
348
451
  if (unparsed_values[index])
@@ -367,10 +470,9 @@ module Addressable
367
470
  value = Addressable::URI.unencode_component(value)
368
471
  end
369
472
  end
370
- if mapping[name] == nil || mapping[name] == value
473
+ if !mapping.has_key?(name) || mapping[name].nil?
474
+ # Doesn't exist, set to value (even if value is nil)
371
475
  mapping[name] = value
372
- else
373
- return nil
374
476
  end
375
477
  index = index + 1
376
478
  end
@@ -387,6 +489,8 @@ module Addressable
387
489
  # @param [Hash] mapping The mapping that corresponds to the pattern.
388
490
  # @param [#validate, #transform] processor
389
491
  # An optional processor object may be supplied.
492
+ # @param [Boolean] normalize_values
493
+ # Optional flag to enable/disable unicode normalization. Default: true
390
494
  #
391
495
  # The object should respond to either the <tt>validate</tt> or
392
496
  # <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
@@ -417,10 +521,11 @@ module Addressable
417
521
  # "http://example.com/{?one,two,three}/"
418
522
  # ).partial_expand({"one" => "1", "three" => 3}).pattern
419
523
  # #=> "http://example.com/?one=1{&two}&three=3"
420
- def partial_expand(mapping, processor=nil)
524
+ def partial_expand(mapping, processor=nil, normalize_values=true)
421
525
  result = self.pattern.dup
526
+ mapping = normalize_keys(mapping)
422
527
  result.gsub!( EXPRESSION ) do |capture|
423
- transform_partial_capture(mapping, capture, processor)
528
+ transform_partial_capture(mapping, capture, processor, normalize_values)
424
529
  end
425
530
  return Addressable::Template.new(result)
426
531
  end
@@ -431,6 +536,8 @@ module Addressable
431
536
  # @param [Hash] mapping The mapping that corresponds to the pattern.
432
537
  # @param [#validate, #transform] processor
433
538
  # An optional processor object may be supplied.
539
+ # @param [Boolean] normalize_values
540
+ # Optional flag to enable/disable unicode normalization. Default: true
434
541
  #
435
542
  # The object should respond to either the <tt>validate</tt> or
436
543
  # <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
@@ -481,11 +588,11 @@ module Addressable
481
588
  # ExampleProcessor
482
589
  # ).to_str
483
590
  # #=> Addressable::Template::InvalidTemplateValueError
484
- def expand(mapping, processor=nil)
591
+ def expand(mapping, processor=nil, normalize_values=true)
485
592
  result = self.pattern.dup
486
593
  mapping = normalize_keys(mapping)
487
594
  result.gsub!( EXPRESSION ) do |capture|
488
- transform_capture(mapping, capture, processor)
595
+ transform_capture(mapping, capture, processor, normalize_values)
489
596
  end
490
597
  return Addressable::URI.parse(result)
491
598
  end
@@ -501,6 +608,7 @@ module Addressable
501
608
  @variables ||= ordered_variable_defaults.map { |var, val| var }.uniq
502
609
  end
503
610
  alias_method :keys, :variables
611
+ alias_method :names, :variables
504
612
 
505
613
  ##
506
614
  # Returns a mapping of variables to their default values specified
@@ -512,17 +620,49 @@ module Addressable
512
620
  Hash[*ordered_variable_defaults.reject { |k, v| v.nil? }.flatten]
513
621
  end
514
622
 
623
+ ##
624
+ # Coerces a template into a `Regexp` object. This regular expression will
625
+ # behave very similarly to the actual template, and should match the same
626
+ # URI values, but it cannot fully handle, for example, values that would
627
+ # extract to an `Array`.
628
+ #
629
+ # @return [Regexp] A regular expression which should match the template.
630
+ def to_regexp
631
+ _, source = parse_template_pattern(pattern)
632
+ Regexp.new(source)
633
+ end
634
+
635
+ ##
636
+ # Returns the source of the coerced `Regexp`.
637
+ #
638
+ # @return [String] The source of the `Regexp` given by {#to_regexp}.
639
+ #
640
+ # @api private
641
+ def source
642
+ self.to_regexp.source
643
+ end
644
+
645
+ ##
646
+ # Returns the named captures of the coerced `Regexp`.
647
+ #
648
+ # @return [Hash] The named captures of the `Regexp` given by {#to_regexp}.
649
+ #
650
+ # @api private
651
+ def named_captures
652
+ self.to_regexp.named_captures
653
+ end
654
+
515
655
  private
516
656
  def ordered_variable_defaults
517
- @ordered_variable_defaults ||= (
657
+ @ordered_variable_defaults ||= begin
518
658
  expansions, _ = parse_template_pattern(pattern)
519
- expansions.map do |capture|
659
+ expansions.flat_map do |capture|
520
660
  _, _, varlist = *capture.match(EXPRESSION)
521
661
  varlist.split(',').map do |varspec|
522
662
  varspec[VARSPEC, 1]
523
663
  end
524
- end.flatten
525
- )
664
+ end
665
+ end
526
666
  end
527
667
 
528
668
 
@@ -535,6 +675,8 @@ module Addressable
535
675
  # The expression to expand
536
676
  # @param [#validate, #transform] processor
537
677
  # An optional processor object may be supplied.
678
+ # @param [Boolean] normalize_values
679
+ # Optional flag to enable/disable unicode normalization. Default: true
538
680
  #
539
681
  # The object should respond to either the <tt>validate</tt> or
540
682
  # <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
@@ -549,21 +691,36 @@ module Addressable
549
691
  # after sending the value to the transform method.
550
692
  #
551
693
  # @return [String] The expanded expression
552
- def transform_partial_capture(mapping, capture, processor = nil)
694
+ def transform_partial_capture(mapping, capture, processor = nil,
695
+ normalize_values = true)
553
696
  _, operator, varlist = *capture.match(EXPRESSION)
554
- is_first = true
555
- varlist.split(',').inject('') do |acc, varspec|
556
- _, name, _ = *varspec.match(VARSPEC)
557
- value = mapping[name]
558
- if value
559
- operator = '&' if !is_first && operator == '?'
560
- acc << transform_capture(mapping, "{#{operator}#{varspec}}", processor)
561
- else
562
- operator = '&' if !is_first && operator == '?'
563
- acc << "{#{operator}#{varspec}}"
564
- end
565
- is_first = false
566
- acc
697
+
698
+ vars = varlist.split(",")
699
+
700
+ if operator == "?"
701
+ # partial expansion of form style query variables sometimes requires a
702
+ # slight reordering of the variables to produce a valid url.
703
+ first_to_expand = vars.find { |varspec|
704
+ _, name, _ = *varspec.match(VARSPEC)
705
+ mapping.key?(name) && !mapping[name].nil?
706
+ }
707
+
708
+ vars = [first_to_expand] + vars.reject {|varspec| varspec == first_to_expand} if first_to_expand
709
+ end
710
+
711
+ vars.
712
+ inject("".dup) do |acc, varspec|
713
+ _, name, _ = *varspec.match(VARSPEC)
714
+ next_val = if mapping.key? name
715
+ transform_capture(mapping, "{#{operator}#{varspec}}",
716
+ processor, normalize_values)
717
+ else
718
+ "{#{operator}#{varspec}}"
719
+ end
720
+ # If we've already expanded at least one '?' operator with non-empty
721
+ # value, change to '&'
722
+ operator = "&" if (operator == "?") && (next_val != "")
723
+ acc << next_val
567
724
  end
568
725
  end
569
726
 
@@ -576,6 +733,9 @@ module Addressable
576
733
  # The expression to replace
577
734
  # @param [#validate, #transform] processor
578
735
  # An optional processor object may be supplied.
736
+ # @param [Boolean] normalize_values
737
+ # Optional flag to enable/disable unicode normalization. Default: true
738
+ #
579
739
  #
580
740
  # The object should respond to either the <tt>validate</tt> or
581
741
  # <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
@@ -590,7 +750,8 @@ module Addressable
590
750
  # after sending the value to the transform method.
591
751
  #
592
752
  # @return [String] The expanded expression
593
- def transform_capture(mapping, capture, processor=nil)
753
+ def transform_capture(mapping, capture, processor=nil,
754
+ normalize_values=true)
594
755
  _, operator, varlist = *capture.match(EXPRESSION)
595
756
  return_value = varlist.split(',').inject([]) do |acc, varspec|
596
757
  _, name, modifier = *varspec.match(VARSPEC)
@@ -610,7 +771,7 @@ module Addressable
610
771
  "Can't convert #{value.class} into String or Array."
611
772
  end
612
773
 
613
- value = normalize_value(value)
774
+ value = normalize_value(value) if normalize_values
614
775
 
615
776
  if processor == nil || !processor.respond_to?(:transform)
616
777
  # Handle percent escaping
@@ -673,7 +834,9 @@ module Addressable
673
834
  end
674
835
  if processor.respond_to?(:transform)
675
836
  transformed_value = processor.transform(name, value)
676
- transformed_value = normalize_value(transformed_value)
837
+ if normalize_values
838
+ transformed_value = normalize_value(transformed_value)
839
+ end
677
840
  end
678
841
  end
679
842
  acc << [name, transformed_value]
@@ -729,25 +892,24 @@ module Addressable
729
892
  # operator.
730
893
  #
731
894
  # @param [Hash, Array, String] value
732
- # Normalizes keys and values with IDNA#unicode_normalize_kc
895
+ # Normalizes unicode keys and values with String#unicode_normalize (NFC)
733
896
  #
734
897
  # @return [Hash, Array, String] The normalized values
735
898
  def normalize_value(value)
736
- unless value.is_a?(Hash)
737
- value = value.respond_to?(:to_ary) ? value.to_ary : value.to_str
738
- end
739
-
740
899
  # Handle unicode normalization
741
- if value.kind_of?(Array)
742
- value.map! { |val| Addressable::IDNA.unicode_normalize_kc(val) }
900
+ if value.respond_to?(:to_ary)
901
+ value.to_ary.map! { |val| normalize_value(val) }
743
902
  elsif value.kind_of?(Hash)
744
903
  value = value.inject({}) { |acc, (k, v)|
745
- acc[Addressable::IDNA.unicode_normalize_kc(k)] =
746
- Addressable::IDNA.unicode_normalize_kc(v)
904
+ acc[normalize_value(k)] = normalize_value(v)
747
905
  acc
748
906
  }
749
907
  else
750
- value = Addressable::IDNA.unicode_normalize_kc(value)
908
+ value = value.to_s if !value.kind_of?(String)
909
+ if value.encoding != Encoding::UTF_8
910
+ value = value.dup.force_encoding(Encoding::UTF_8)
911
+ end
912
+ value = value.unicode_normalize(:nfc)
751
913
  end
752
914
  value
753
915
  end
@@ -775,15 +937,35 @@ module Addressable
775
937
  end
776
938
  end
777
939
 
940
+ ##
941
+ # Generates the <tt>Regexp</tt> that parses a template pattern. Memoizes the
942
+ # value if template processor not set (processors may not be deterministic)
943
+ #
944
+ # @param [String] pattern The URI template pattern.
945
+ # @param [#match] processor The template processor to use.
946
+ #
947
+ # @return [Array, Regexp]
948
+ # An array of expansion variables nad a regular expression which may be
949
+ # used to parse a template pattern
950
+ def parse_template_pattern(pattern, processor = nil)
951
+ if processor.nil? && pattern == @pattern
952
+ @cached_template_parse ||=
953
+ parse_new_template_pattern(pattern, processor)
954
+ else
955
+ parse_new_template_pattern(pattern, processor)
956
+ end
957
+ end
958
+
778
959
  ##
779
960
  # Generates the <tt>Regexp</tt> that parses a template pattern.
780
961
  #
781
962
  # @param [String] pattern The URI template pattern.
782
963
  # @param [#match] processor The template processor to use.
783
964
  #
784
- # @return [Regexp]
785
- # A regular expression which may be used to parse a template pattern.
786
- def parse_template_pattern(pattern, processor=nil)
965
+ # @return [Array, Regexp]
966
+ # An array of expansion variables nad a regular expression which may be
967
+ # used to parse a template pattern
968
+ def parse_new_template_pattern(pattern, processor = nil)
787
969
  # Escape the pattern. The two gsubs restore the escaped curly braces
788
970
  # back to their original form. Basically, escape everything that isn't
789
971
  # within an expansion.
@@ -803,10 +985,12 @@ module Addressable
803
985
  _, operator, varlist = *expansion.match(EXPRESSION)
804
986
  leader = Regexp.escape(LEADERS.fetch(operator, ''))
805
987
  joiner = Regexp.escape(JOINERS.fetch(operator, ','))
806
- leader + varlist.split(',').map do |varspec|
988
+ combined = varlist.split(',').map do |varspec|
807
989
  _, name, modifier = *varspec.match(VARSPEC)
808
- if processor != nil && processor.respond_to?(:match)
809
- "(#{ processor.match(name) })"
990
+
991
+ result = processor && processor.respond_to?(:match) ? processor.match(name) : nil
992
+ if result
993
+ "(?<#{name}>#{ result })"
810
994
  else
811
995
  group = case operator
812
996
  when '+'
@@ -827,16 +1011,17 @@ module Addressable
827
1011
  "#{ UNRESERVED }*?"
828
1012
  end
829
1013
  if modifier == '*'
830
- "(#{group}(?:#{joiner}?#{group})*)?"
1014
+ "(?<#{name}>#{group}(?:#{joiner}?#{group})*)?"
831
1015
  else
832
- "(#{group})?"
1016
+ "(?<#{name}>#{group})?"
833
1017
  end
834
1018
  end
835
1019
  end.join("#{joiner}?")
1020
+ "(?:|#{leader}#{combined})"
836
1021
  end
837
1022
 
838
1023
  # Ensure that the regular expression matches the whole URI.
839
- regexp_string = "^#{regexp_string}$"
1024
+ regexp_string = "\\A#{regexp_string}\\z"
840
1025
  return expansions, Regexp.new(regexp_string)
841
1026
  end
842
1027