addressable 2.5.0 → 2.8.8

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,4 +1,5 @@
1
- # encoding:utf-8
1
+ # frozen_string_literal: true
2
+
2
3
  #--
3
4
  # Copyright (C) Bob Aman
4
5
  #
@@ -1,4 +1,5 @@
1
- # encoding:utf-8
1
+ # frozen_string_literal: true
2
+
2
3
  #--
3
4
  # Copyright (C) Bob Aman
4
5
  #
@@ -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 =
@@ -156,7 +157,7 @@ module Addressable
156
157
  # the ::MatchData#[] behavior.
157
158
  #
158
159
  # @param [#to_int, nil] len
159
- # If provided, an array of values will be returend with the given
160
+ # If provided, an array of values will be returned with the given
160
161
  # parameter used as length.
161
162
  #
162
163
  # @return [Array, String, nil]
@@ -410,7 +411,7 @@ module Addressable
410
411
  # match.captures
411
412
  # #=> ["a", ["b", "c"]]
412
413
  def match(uri, processor=nil)
413
- uri = Addressable::URI.parse(uri)
414
+ uri = Addressable::URI.parse(uri) unless uri.is_a?(Addressable::URI)
414
415
  mapping = {}
415
416
 
416
417
  # First, we need to process the pattern, and extract the values.
@@ -488,6 +489,8 @@ module Addressable
488
489
  # @param [Hash] mapping The mapping that corresponds to the pattern.
489
490
  # @param [#validate, #transform] processor
490
491
  # An optional processor object may be supplied.
492
+ # @param [Boolean] normalize_values
493
+ # Optional flag to enable/disable unicode normalization. Default: true
491
494
  #
492
495
  # The object should respond to either the <tt>validate</tt> or
493
496
  # <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
@@ -518,11 +521,11 @@ module Addressable
518
521
  # "http://example.com/{?one,two,three}/"
519
522
  # ).partial_expand({"one" => "1", "three" => 3}).pattern
520
523
  # #=> "http://example.com/?one=1{&two}&three=3"
521
- def partial_expand(mapping, processor=nil)
524
+ def partial_expand(mapping, processor=nil, normalize_values=true)
522
525
  result = self.pattern.dup
523
526
  mapping = normalize_keys(mapping)
524
527
  result.gsub!( EXPRESSION ) do |capture|
525
- transform_partial_capture(mapping, capture, processor)
528
+ transform_partial_capture(mapping, capture, processor, normalize_values)
526
529
  end
527
530
  return Addressable::Template.new(result)
528
531
  end
@@ -533,6 +536,8 @@ module Addressable
533
536
  # @param [Hash] mapping The mapping that corresponds to the pattern.
534
537
  # @param [#validate, #transform] processor
535
538
  # An optional processor object may be supplied.
539
+ # @param [Boolean] normalize_values
540
+ # Optional flag to enable/disable unicode normalization. Default: true
536
541
  #
537
542
  # The object should respond to either the <tt>validate</tt> or
538
543
  # <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
@@ -583,11 +588,11 @@ module Addressable
583
588
  # ExampleProcessor
584
589
  # ).to_str
585
590
  # #=> Addressable::Template::InvalidTemplateValueError
586
- def expand(mapping, processor=nil)
591
+ def expand(mapping, processor=nil, normalize_values=true)
587
592
  result = self.pattern.dup
588
593
  mapping = normalize_keys(mapping)
589
594
  result.gsub!( EXPRESSION ) do |capture|
590
- transform_capture(mapping, capture, processor)
595
+ transform_capture(mapping, capture, processor, normalize_values)
591
596
  end
592
597
  return Addressable::URI.parse(result)
593
598
  end
@@ -647,50 +652,16 @@ module Addressable
647
652
  self.to_regexp.named_captures
648
653
  end
649
654
 
650
- ##
651
- # Generates a route result for a given set of parameters.
652
- # Should only be used by rack-mount.
653
- #
654
- # @param params [Hash] The set of parameters used to expand the template.
655
- # @param recall [Hash] Default parameters used to expand the template.
656
- # @param options [Hash] Either a `:processor` or a `:parameterize` block.
657
- #
658
- # @api private
659
- def generate(params={}, recall={}, options={})
660
- merged = recall.merge(params)
661
- if options[:processor]
662
- processor = options[:processor]
663
- elsif options[:parameterize]
664
- # TODO: This is sending me into fits trying to shoe-horn this into
665
- # the existing API. I think I've got this backwards and processors
666
- # should be a set of 4 optional blocks named :validate, :transform,
667
- # :match, and :restore. Having to use a singleton here is a huge
668
- # code smell.
669
- processor = Object.new
670
- class <<processor
671
- attr_accessor :block
672
- def transform(name, value)
673
- block.call(name, value)
674
- end
675
- end
676
- processor.block = options[:parameterize]
677
- else
678
- processor = nil
679
- end
680
- result = self.expand(merged, processor)
681
- result.to_s if result
682
- end
683
-
684
655
  private
685
656
  def ordered_variable_defaults
686
657
  @ordered_variable_defaults ||= begin
687
658
  expansions, _ = parse_template_pattern(pattern)
688
- expansions.map do |capture|
659
+ expansions.flat_map do |capture|
689
660
  _, _, varlist = *capture.match(EXPRESSION)
690
661
  varlist.split(',').map do |varspec|
691
662
  varspec[VARSPEC, 1]
692
663
  end
693
- end.flatten
664
+ end
694
665
  end
695
666
  end
696
667
 
@@ -704,6 +675,8 @@ module Addressable
704
675
  # The expression to expand
705
676
  # @param [#validate, #transform] processor
706
677
  # An optional processor object may be supplied.
678
+ # @param [Boolean] normalize_values
679
+ # Optional flag to enable/disable unicode normalization. Default: true
707
680
  #
708
681
  # The object should respond to either the <tt>validate</tt> or
709
682
  # <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
@@ -718,56 +691,36 @@ module Addressable
718
691
  # after sending the value to the transform method.
719
692
  #
720
693
  # @return [String] The expanded expression
721
- def transform_partial_capture(mapping, capture, processor = nil)
694
+ def transform_partial_capture(mapping, capture, processor = nil,
695
+ normalize_values = true)
722
696
  _, operator, varlist = *capture.match(EXPRESSION)
723
697
 
724
- vars = varlist.split(',')
698
+ vars = varlist.split(",")
725
699
 
726
- if '?' == operator
700
+ if operator == "?"
727
701
  # partial expansion of form style query variables sometimes requires a
728
702
  # slight reordering of the variables to produce a valid url.
729
703
  first_to_expand = vars.find { |varspec|
730
704
  _, name, _ = *varspec.match(VARSPEC)
731
- mapping.key? name
705
+ mapping.key?(name) && !mapping[name].nil?
732
706
  }
733
707
 
734
708
  vars = [first_to_expand] + vars.reject {|varspec| varspec == first_to_expand} if first_to_expand
735
709
  end
736
710
 
737
- vars
738
- .zip(operator_sequence(operator).take(vars.length))
739
- .reduce("") do |acc, (varspec, op)|
711
+ vars.
712
+ inject("".dup) do |acc, varspec|
740
713
  _, name, _ = *varspec.match(VARSPEC)
741
-
742
- acc << if mapping.key? name
743
- transform_capture(mapping, "{#{op}#{varspec}}", processor)
744
- else
745
- "{#{op}#{varspec}}"
746
- end
747
- end
748
- end
749
-
750
- ##
751
- # Creates a lazy Enumerator of the operators that should be used to expand
752
- # variables in a varlist starting with `operator`. For example, an operator
753
- # `"?"` results in the sequence `"?","&","&"...`
754
- #
755
- # @param [String] operator from which to generate a sequence
756
- #
757
- # @return [Enumerator] sequence of operators
758
- def operator_sequence(operator)
759
- rest_operator = if "?" == operator
760
- "&"
761
- else
762
- operator
763
- end
764
- head_operator = operator
765
-
766
- Enumerator.new do |y|
767
- y << head_operator.to_s
768
- while true
769
- y << rest_operator.to_s
770
- end
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
771
724
  end
772
725
  end
773
726
 
@@ -780,6 +733,9 @@ module Addressable
780
733
  # The expression to replace
781
734
  # @param [#validate, #transform] processor
782
735
  # An optional processor object may be supplied.
736
+ # @param [Boolean] normalize_values
737
+ # Optional flag to enable/disable unicode normalization. Default: true
738
+ #
783
739
  #
784
740
  # The object should respond to either the <tt>validate</tt> or
785
741
  # <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
@@ -794,7 +750,8 @@ module Addressable
794
750
  # after sending the value to the transform method.
795
751
  #
796
752
  # @return [String] The expanded expression
797
- def transform_capture(mapping, capture, processor=nil)
753
+ def transform_capture(mapping, capture, processor=nil,
754
+ normalize_values=true)
798
755
  _, operator, varlist = *capture.match(EXPRESSION)
799
756
  return_value = varlist.split(',').inject([]) do |acc, varspec|
800
757
  _, name, modifier = *varspec.match(VARSPEC)
@@ -814,7 +771,7 @@ module Addressable
814
771
  "Can't convert #{value.class} into String or Array."
815
772
  end
816
773
 
817
- value = normalize_value(value)
774
+ value = normalize_value(value) if normalize_values
818
775
 
819
776
  if processor == nil || !processor.respond_to?(:transform)
820
777
  # Handle percent escaping
@@ -877,7 +834,9 @@ module Addressable
877
834
  end
878
835
  if processor.respond_to?(:transform)
879
836
  transformed_value = processor.transform(name, value)
880
- transformed_value = normalize_value(transformed_value)
837
+ if normalize_values
838
+ transformed_value = normalize_value(transformed_value)
839
+ end
881
840
  end
882
841
  end
883
842
  acc << [name, transformed_value]
@@ -933,25 +892,24 @@ module Addressable
933
892
  # operator.
934
893
  #
935
894
  # @param [Hash, Array, String] value
936
- # Normalizes keys and values with IDNA#unicode_normalize_kc
895
+ # Normalizes unicode keys and values with String#unicode_normalize (NFC)
937
896
  #
938
897
  # @return [Hash, Array, String] The normalized values
939
898
  def normalize_value(value)
940
- unless value.is_a?(Hash)
941
- value = value.respond_to?(:to_ary) ? value.to_ary : value.to_str
942
- end
943
-
944
899
  # Handle unicode normalization
945
- if value.kind_of?(Array)
946
- 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) }
947
902
  elsif value.kind_of?(Hash)
948
903
  value = value.inject({}) { |acc, (k, v)|
949
- acc[Addressable::IDNA.unicode_normalize_kc(k)] =
950
- Addressable::IDNA.unicode_normalize_kc(v)
904
+ acc[normalize_value(k)] = normalize_value(v)
951
905
  acc
952
906
  }
953
907
  else
954
- 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)
955
913
  end
956
914
  value
957
915
  end
@@ -979,15 +937,35 @@ module Addressable
979
937
  end
980
938
  end
981
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
+
982
959
  ##
983
960
  # Generates the <tt>Regexp</tt> that parses a template pattern.
984
961
  #
985
962
  # @param [String] pattern The URI template pattern.
986
963
  # @param [#match] processor The template processor to use.
987
964
  #
988
- # @return [Regexp]
989
- # A regular expression which may be used to parse a template pattern.
990
- 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)
991
969
  # Escape the pattern. The two gsubs restore the escaped curly braces
992
970
  # back to their original form. Basically, escape everything that isn't
993
971
  # within an expansion.
@@ -1043,7 +1021,7 @@ module Addressable
1043
1021
  end
1044
1022
 
1045
1023
  # Ensure that the regular expression matches the whole URI.
1046
- regexp_string = "^#{regexp_string}$"
1024
+ regexp_string = "\\A#{regexp_string}\\z"
1047
1025
  return expansions, Regexp.new(regexp_string)
1048
1026
  end
1049
1027