uri_template 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.
data/CHANGELOG CHANGED
@@ -1,3 +1,9 @@
1
+ # 0.1.0 - 2.11.2011
2
+ - Removed Sections. They made too many headaches.
3
+ + Made draft7 template concatenateable. This should replace sections.
4
+ * BUGFIX: multiline uris were matched
5
+ * BUGFIX: variablenames were decoded when this was not appreciated
6
+
1
7
  # 0.0.2 - 1.11.2011
2
8
  * BUGFIX: Concatenating empty sections no more leads to catch-all templates, when an emtpy template was appreciated.
3
9
  + The extracted variables now contains the keys :suffix and :prefix if the match didn't consume the whole uri.
data/lib/uri_template.rb CHANGED
@@ -18,6 +18,7 @@
18
18
  # A base module for all implementations of a uri template.
19
19
  module URITemplate
20
20
 
21
+ autoload :Utils, 'uri_template/utils'
21
22
  autoload :Draft7, 'uri_template/draft7'
22
23
 
23
24
  # A hash with all available implementations.
@@ -39,6 +40,7 @@ module URITemplate
39
40
  # URITemplate.resolve_class("template",:draft7) #=> [ URITemplate::Draft7, ["template"] ]
40
41
  #
41
42
  # @raise ArgumentError when no class was found.
43
+ #
42
44
  def self.resolve_class(*args)
43
45
  symbols, rest = args.partition{|x| x.kind_of? Symbol }
44
46
  version = symbols.fetch(0, :default)
@@ -66,45 +68,18 @@ module URITemplate
66
68
  module Invalid
67
69
  end
68
70
 
69
- # A base module for all implementation of a template section.
70
- # Sections are a custom extension to the uri template spec.
71
- # A template section ( in comparison to a template ) can be unbounded on its ends. Therefore they don't necessarily match a whole uri and can be concatenated.
72
- module Section
73
-
74
- include URITemplate
75
-
76
- # Same as {URITemplate.new} but for sections
77
- def self.new(*args)
78
- klass, rest = URITemplate.resolve_class(*args)
79
- return klass::Section.new(*rest)
80
- end
81
-
82
- # @abstract
83
- # Concatenates this section with an other section.
84
- def >>(other)
85
- raise "Please implement #>> on #{self.class.inspect}"
86
- end
87
-
88
- # @abstract
89
- # Is this section left bounded?
90
- def left_bound?
91
- raise "Please implement #left_bound? on #{self.class.inspect}"
92
- end
93
-
94
- # @abstract
95
- # Is this section right bounded?
96
- def right_bound?
97
- raise "Please implement #right_bound? on #{self.class.inspect}"
98
- end
99
-
100
- end
101
-
102
71
  # @abstract
103
72
  # Expands this uri template with the given variables.
104
73
  # The variables should be converted to strings using {Utils#object_to_param}.
105
- # @raise Unconvertable if a variable could not be converted.
74
+ # @raise {Unconvertable} if a variable could not be converted to a string.
106
75
  def expand(variables={})
107
- raise "Please implement #expand on #{self.class.inspect}"
76
+ raise "Please implement #expand on #{self.class.inspect}."
77
+ end
78
+
79
+ # @abstract
80
+ # Returns the type of this template. The type is a symbol which can be used in {.resolve_class} to resolve the type of this template.
81
+ def type
82
+ raise "Please implement #type on #{self.class.inspect}."
108
83
  end
109
84
 
110
85
  end
File without changes
File without changes
@@ -34,7 +34,7 @@ class URITemplate::Draft7
34
34
  Utils = URITemplate::Utils
35
35
 
36
36
  # @private
37
- LITERAL = /^([^"'%<>\\^`{|}\s]|%\h\h)+/
37
+ LITERAL = /^([^"'%<>\\^`{|}\s\p{Cc}]|%\h\h)+/
38
38
 
39
39
  # @private
40
40
  CHARACTER_CLASSES = {
@@ -60,19 +60,19 @@ class URITemplate::Draft7
60
60
  }
61
61
 
62
62
  # Specifies that no processing should be done upon extraction.
63
- # @see extract
63
+ # @see #extract
64
64
  NO_PROCESSING = []
65
65
 
66
66
  # Specifies that the extracted values should be processed.
67
- # @see extract
67
+ # @see #extract
68
68
  CONVERT_VALUES = [:convert_values]
69
69
 
70
70
  # Specifies that the extracted variable list should be processed.
71
- # @see extract
71
+ # @see #extract
72
72
  CONVERT_RESULT = [:convert_result]
73
73
 
74
74
  # Default processing. Means: convert values and the list itself.
75
- # @see extract
75
+ # @see #extract
76
76
  DEFAULT_PROCESSING = CONVERT_VALUES + CONVERT_RESULT
77
77
 
78
78
  # @private
@@ -99,11 +99,15 @@ __REGEXP__
99
99
  (?<varchar> [a-zA-Z_]|%[0-9a-fA-F]{2}){0}
100
100
  (?<varname> \g<varchar>(?:\g<varchar>|\.)*){0}
101
101
  (?<varspec> \g<varname>\*?(?::\d+)?){0}
102
- ^(([^"'%<>^`{|}\s]|%\h\h)+|\{\g<operator>(?<vars>\g<varspec>(?:,\g<varspec>)*)\})*$
102
+ ^(([^"'%<>^`{|}\s\p{Cc}]|%\h\h)+|\{\g<operator>(?<vars>\g<varspec>(?:,\g<varspec>)*)\})*$
103
103
  __REGEXP__
104
104
 
105
105
  # @private
106
- class Literal
106
+ class Token
107
+ end
108
+
109
+ # @private
110
+ class Literal < Token
107
111
 
108
112
  attr_reader :string
109
113
 
@@ -115,6 +119,10 @@ __REGEXP__
115
119
  0
116
120
  end
117
121
 
122
+ def level
123
+ 1
124
+ end
125
+
118
126
  def expand(*_)
119
127
  return @string
120
128
  end
@@ -128,58 +136,9 @@ __REGEXP__
128
136
  end
129
137
 
130
138
  end
131
-
132
- # @private
133
- class Terminal
134
- def expand(*_)
135
- ''
136
- end
137
-
138
- def size
139
- 0
140
- end
141
- end
142
-
143
- # @private
144
- class LeftBound < Terminal
145
-
146
- def to_r_source(*_)
147
- '^'
148
- end
149
-
150
- def to_s
151
- ''
152
- end
153
-
154
- end
155
-
156
- # @private
157
- class RightBound < Terminal
158
-
159
- def to_r_source(*_)
160
- '$'
161
- end
162
-
163
- def to_s
164
- ''
165
- end
166
-
167
- end
168
-
169
- # @private
170
- class Open < Terminal
171
-
172
- def to_r_source(*_)
173
- ''
174
- end
175
-
176
- def to_s
177
- "\u2026"
178
- end
179
- end
180
-
139
+
181
140
  # @private
182
- class Expression
141
+ class Expression < Token
183
142
 
184
143
  attr_reader :variables, :max_length
185
144
 
@@ -192,6 +151,7 @@ __REGEXP__
192
151
  PAIR_CONNECTOR = '='.freeze
193
152
  PAIR_IF_EMPTY = true
194
153
  LIST_CONNECTOR = ','.freeze
154
+ BASE_LEVEL = 1
195
155
 
196
156
  CHARACTER_CLASS = CHARACTER_CLASSES[:unreserved]
197
157
 
@@ -202,6 +162,18 @@ __REGEXP__
202
162
  @variables.size
203
163
  end
204
164
 
165
+ def level
166
+ if @variables.none?{|_,expand,ml| expand || (ml > 0) }
167
+ if @variables.size == 1
168
+ return self.class::BASE_LEVEL
169
+ else
170
+ return 3
171
+ end
172
+ else
173
+ return 4
174
+ end
175
+ end
176
+
205
177
  def expand( vars, options )
206
178
  result = []
207
179
  variables.each{| var, expand , max_length |
@@ -243,7 +215,7 @@ __REGEXP__
243
215
  value = "(?:\\g<#{self.class::CHARACTER_CLASS[:class_name]}>|,)#{(max_length > 0)?'{,'+max_length.to_s+'}':'*'}"
244
216
  if expand
245
217
  #if self.class::PAIR_IF_EMPTY
246
- pair = "\\g<c_vn_>(?:#{Regexp.escape(self.class::PAIR_CONNECTOR)}#{value})?"
218
+ pair = "(?:\\g<c_vn_>#{Regexp.escape(self.class::PAIR_CONNECTOR)})?#{value}"
247
219
 
248
220
  if first
249
221
  source << "(?<v#{base_counter + i}>(?:#{pair})(?:#{Regexp.escape(self.class::SEPARATOR)}#{pair})*)"
@@ -274,7 +246,7 @@ __REGEXP__
274
246
  # could be list or map, too
275
247
  value = "\\g<#{self.class::CHARACTER_CLASS[:class_name]}>#{(max_length > 0)?'{,'+max_length.to_s+'}':'*'}"
276
248
 
277
- pair = "\\g<c_vn_>(?:#{Regexp.escape(self.class::PAIR_CONNECTOR)}#{value})?"
249
+ pair = "(?:\\g<c_vn_>#{Regexp.escape(self.class::PAIR_CONNECTOR)})?#{value}"
278
250
 
279
251
  value = "#{pair}(?:#{Regexp.escape(self.class::SEPARATOR)}#{pair})*"
280
252
  elsif last
@@ -323,12 +295,12 @@ __REGEXP__
323
295
  found_value = true
324
296
  splitted << [ match['name'][0..-2], decode(match['value'] + rest , false) ]
325
297
  else
326
- splitted << [ decode(match['value'] + rest , false), nil ]
298
+ splitted << [ match['value'] + rest, nil ]
327
299
  end
328
300
  rest = match.post_match
329
301
  end
330
302
  if !found_value
331
- return [ [ name, splitted.map{|n,v| v || n } ] ]
303
+ return [ [ name, splitted.map{|n,v| decode(n , false) } ] ]
332
304
  else
333
305
  return [ [ name, splitted ] ]
334
306
  end
@@ -433,6 +405,7 @@ __REGEXP__
433
405
 
434
406
  CHARACTER_CLASS = CHARACTER_CLASSES[:unreserved_reserved_pct]
435
407
  OPERATOR = '+'.freeze
408
+ BASE_LEVEL = 2
436
409
 
437
410
  end
438
411
 
@@ -441,6 +414,7 @@ __REGEXP__
441
414
  CHARACTER_CLASS = CHARACTER_CLASSES[:unreserved_reserved_pct]
442
415
  PREFIX = '#'.freeze
443
416
  OPERATOR = '#'.freeze
417
+ BASE_LEVEL = 2
444
418
 
445
419
  end
446
420
 
@@ -449,6 +423,7 @@ __REGEXP__
449
423
  SEPARATOR = '.'.freeze
450
424
  PREFIX = '.'.freeze
451
425
  OPERATOR = '.'.freeze
426
+ BASE_LEVEL = 3
452
427
 
453
428
  end
454
429
 
@@ -457,6 +432,7 @@ __REGEXP__
457
432
  SEPARATOR = '/'.freeze
458
433
  PREFIX = '/'.freeze
459
434
  OPERATOR = '/'.freeze
435
+ BASE_LEVEL = 3
460
436
 
461
437
  end
462
438
 
@@ -467,6 +443,7 @@ __REGEXP__
467
443
  NAMED = true
468
444
  PAIR_IF_EMPTY = false
469
445
  OPERATOR = ';'.freeze
446
+ BASE_LEVEL = 3
470
447
 
471
448
  end
472
449
 
@@ -476,6 +453,7 @@ __REGEXP__
476
453
  PREFIX = '?'.freeze
477
454
  NAMED = true
478
455
  OPERATOR = '?'.freeze
456
+ BASE_LEVEL = 3
479
457
 
480
458
  end
481
459
 
@@ -485,6 +463,7 @@ __REGEXP__
485
463
  PREFIX = '&'.freeze
486
464
  NAMED = true
487
465
  OPERATOR = '&'.freeze
466
+ BASE_LEVEL = 3
488
467
 
489
468
  end
490
469
 
@@ -559,6 +538,7 @@ __REGEXP__
559
538
 
560
539
  # Tries to convert the given param in to a instance of {Draft7}
561
540
  # It basically passes thru instances of that class, parses strings and return nil on everything else.
541
+ #
562
542
  # @example
563
543
  # URITemplate::Draft7.try_convert( Object.new ) #=> nil
564
544
  # tpl = URITemplate::Draft7.new('{foo}')
@@ -566,6 +546,7 @@ __REGEXP__
566
546
  # URITemplate::Draft7.try_convert('{foo}') #=> tpl
567
547
  # # This pattern is invalid, so it wont be parsed:
568
548
  # URITemplate::Draft7.try_convert('{foo') #=> nil
549
+ #
569
550
  def try_convert(x)
570
551
  if x.kind_of? self
571
552
  return x
@@ -576,6 +557,19 @@ __REGEXP__
576
557
  end
577
558
  end
578
559
 
560
+
561
+ # Like {.try_convert}, but raises an ArgumentError, when the conversion failed.
562
+ #
563
+ # @raise ArgumentError
564
+ def convert(x)
565
+ o = self.try_convert(x)
566
+ if o.nil?
567
+ raise ArgumentError, "Expected to receive something that can be converted to an #{self.class}, but got: #{x.inspect}."
568
+ else
569
+ return o
570
+ end
571
+ end
572
+
579
573
  # Tests whether a given pattern is a valid template pattern.
580
574
  # @example
581
575
  # URITemplate::Draft7.valid? 'foo' #=> true
@@ -663,28 +657,49 @@ __REGEXP__
663
657
  def to_r
664
658
  classes = CHARACTER_CLASSES.map{|_,v| v[:class]+"{0}\n" }
665
659
  bc = 0
666
- @regexp ||= Regexp.new(classes.join + tokens.map{|part|
660
+ @regexp ||= Regexp.new(classes.join + '\A' + tokens.map{|part|
667
661
  r = part.to_r_source(bc)
668
662
  bc += part.size
669
663
  r
670
- }.join, Regexp::EXTENDED)
664
+ }.join + '\z' , Regexp::EXTENDED)
671
665
  end
672
666
 
673
667
 
674
668
  # Extracts variables from a uri ( given as string ) or an instance of MatchData ( which was matched by the regexp of this template.
675
- # The actual result depends on the value of @p post_processing.
669
+ # The actual result depends on the value of post_processing.
676
670
  # This argument specifies whether pair arrays should be converted to hashes.
677
671
  #
678
- # @example
672
+ # @example Default Processing
679
673
  # URITemplate::Draft7.new('{var}').extract('value') #=> {'var'=>'value'}
680
674
  # URITemplate::Draft7.new('{&args*}').extract('&a=1&b=2') #=> {'args'=>{'a'=>'1','b'=>'2'}}
681
675
  # URITemplate::Draft7.new('{&arg,arg}').extract('&arg=1&arg=2') #=> {'arg'=>'2'}
682
676
  #
683
- # @example
677
+ # @example No Processing
684
678
  # URITemplate::Draft7.new('{var}').extract('value', URITemplate::Draft7::NO_PROCESSING) #=> [['var','value']]
685
679
  # URITemplate::Draft7.new('{&args*}').extract('&a=1&b=2', URITemplate::Draft7::NO_PROCESSING) #=> [['args',[['a','1'],['b','2']]]]
686
680
  # URITemplate::Draft7.new('{&arg,arg}').extract('&arg=1&arg=2', URITemplate::Draft7::NO_PROCESSING) #=> [['arg','1'],['arg','2']]
687
681
  #
682
+ # @raise Encoding::InvalidByteSequenceError when the given uri was not properly encoded.
683
+ # @raise Encoding::UndefinedConversionError when the given uri could not be converted to utf-8.
684
+ # @raise Encoding::CompatibilityError when the given uri could not be converted to utf-8.
685
+ #
686
+ # @param [String,MatchData] Uri_or_MatchData A uri or a matchdata from which the variables should be extracted.
687
+ # @param [Array] Processing Specifies which processing should be done.
688
+ #
689
+ # @note
690
+ # Don't expect that an extraction can fully recover the expanded variables. Extract rather generates a variable list which should expand to the uri from which it were extracted. In general the following equation should hold true:
691
+ # a_tpl.expand( a_tpl.extract( an_uri ) ) == an_uri
692
+ #
693
+ # @example Extraction cruces
694
+ # two_lists = URITemplate::Draft7.new('{listA*,listB*}')
695
+ # uri = two_lists.expand('listA'=>[1,2],'listB'=>[3,4]) #=> "1,2,3,4"
696
+ # variables = two_lists.extract( uri ) #=> {'listA'=>["1","2","3","4"],'listB'=>nil}
697
+ # # However, like said in the note:
698
+ # two_lists.expand( variables ) == uri #=> true
699
+ #
700
+ # @note
701
+ # The current implementation drops duplicated variables instead of checking them.
702
+ #
688
703
  #
689
704
  def extract(uri_or_match, post_processing = DEFAULT_PROCESSING )
690
705
  if uri_or_match.kind_of? String
@@ -703,12 +718,6 @@ __REGEXP__
703
718
  return nil
704
719
  else
705
720
  result = extract_matchdata(m)
706
- if m.pre_match and m.pre_match.size > 0
707
- result.unshift( [:prefix, m.pre_match] )
708
- end
709
- if m.post_match and m.post_match.size > 0
710
- result.push( [:suffix, m.post_match] )
711
- end
712
721
  if post_processing.include? :convert_values
713
722
  result.map!{|k,v| [k, Utils.pair_array_to_hash(v)] }
714
723
  end
@@ -738,140 +747,158 @@ __REGEXP__
738
747
  end
739
748
 
740
749
  alias to_s pattern
750
+
751
+ # Compares two template patterns.
752
+ def ==(tpl)
753
+ return false if self.class != tpl.class
754
+ return self.pattern == tpl.pattern
755
+ end
756
+
757
+ # @method ===(uri)
758
+ # Alias for to_r.=== . Tests whether this template matches a given uri.
759
+ # @return TrueClass, FalseClass
760
+ def_delegators :to_r, :===
761
+
762
+ # @method match(uri)
763
+ # Alias for to_r.match . Matches this template against the given uri.
764
+ # @yield MatchData
765
+ # @return MatchData, Object
766
+ def_delegators :to_r, :match
741
767
 
742
- # Sections are a custom extension to the uri template spec.
743
- # A template section ( in comparison to a template ) can be unbounded on its ends. Therefore they don't necessarily match a whole uri and can be concatenated.
744
- # Unboundedness is denoted with unicode character \u2026 ( … ).
745
- #
746
- # @example
747
- # prefix = URITemplate::Draft7::Section.new('/prefix…')
748
- # template = URITemplate::Draft7.new('/prefix')
749
- # prefix === '/prefix/something completly different' #=> true
750
- # template === '/prefix/something completly different' #=> false
751
- # prefix.to_r.match('/prefix/something completly different').post_match #=> '/something completly different'
752
- #
768
+ # The type of this template.
769
+ #
753
770
  # @example
754
- # prefix = URITemplate::Draft7::Section.new('/prefix…')
755
- # tpl = prefix >> '…/end'
756
- # tpl.pattern #=> '/prefix/end'
757
- #
758
- # This behavior is usefull for building routers:
771
+ # tpl1 = URITemplate::Draft7.new('/foo')
772
+ # tpl2 = URITemplate.new( tpl1.pattern, tpl1.type )
773
+ # tpl1 == tpl2 #=> true
774
+ #
775
+ # @see {URITemplate#type}
776
+ def type
777
+ :draft7
778
+ end
779
+
780
+ # Returns the level of this template according to the draft ( http://tools.ietf.org/html/draft-gregorio-uritemplate-07#section-1.2 ). Higher level means higher complexity.
781
+ # Basically this is defined as:
759
782
  #
783
+ # * Level 1: no operators, one variable per expansion, no variable modifiers
784
+ # * Level 2: '+' and '#' operators, one variable per expansion, no variable modifiers
785
+ # * Level 3: all operators, multiple variables per expansion, no variable modifiers
786
+ # * Level 4: all operators, multiple variables per expansion, all variable modifiers
787
+ #
760
788
  # @example
761
- #
762
- # def route( uri )
763
- # prefixes = [
764
- # [ 'app_a/…' , lambda{|vars, rest| "app_a: #{rest} with #{vars.inspect}" } ],
765
- # [ 'app_b/{x}/…', lambda{|vars, rest| "app_b: #{rest} with #{vars.inspect}" } ]
766
- # ]
767
- # prefixes.each do |tpl, lb|
768
- # tpl = URITemplate::Draft7::Section.new(tpl)
769
- # tpl.match(uri) do |match_data|
770
- # return lb.call(tpl.extract(match_data), match_data.post_match)
771
- # end
772
- # end
773
- # return "not found"
774
- # end
775
- # route( 'app_a/do_something' ) #=> "app_a: do_something with {:suffix=>\"do_something\"}"
776
- # route( 'app_b/1337/something_else' ) #=> "app_b: something_else with {\"x\"=>\"1337\", :suffix=>\"something_else\"}"
777
- # route( 'bla' ) #=> 'not found'
778
- #
779
- class Section < self
780
-
781
- include URITemplate::Section
789
+ # URITemplate::Draft7.new('/foo/').level #=> 1
790
+ # URITemplate::Draft7.new('/foo{bar}').level #=> 1
791
+ # URITemplate::Draft7.new('/foo{#bar}').level #=> 2
792
+ # URITemplate::Draft7.new('/foo{.bar}').level #=> 3
793
+ # URITemplate::Draft7.new('/foo{bar,baz}').level #=> 3
794
+ # URITemplate::Draft7.new('/foo{bar:20}').level #=> 4
795
+ # URITemplate::Draft7.new('/foo{bar*}').level #=> 4
796
+ #
797
+ # Templates of lower levels might be convertible to other formats while templates of higher levels might be incompatible. Level 1 for example should be convertible to any other format since it just contains simple expansions.
798
+ #
799
+ def level
800
+ tokens.map(&:level).max
801
+ end
782
802
 
783
- # The ellipsis character.
784
- ELLIPSIS = "\u2026".freeze
803
+ # Tries to conatenate two templates, as if they were path segments.
804
+ # Removes double slashes or insert one if they are missing.
805
+ #
806
+ # @example
807
+ # tpl = URITemplate::Draft7.new('/xy/')
808
+ # (tpl / '/z/' ).pattern #=> '/xy/z/'
809
+ # (tpl / 'z/' ).pattern #=> '/xy/z/'
810
+ # (tpl / '{/z}' ).pattern #=> '/xy{/z}'
811
+ # (tpl / 'a' / 'b' ).pattern #=> '/xy/a/b'
812
+ #
813
+ def /(o)
814
+ other = self.class.convert(o)
785
815
 
786
- # Is this section left bounded?
787
- def left_bound?
788
- tokens.first.kind_of? LeftBound
816
+ if other.absolute?
817
+ raise ArgumentError, "Expected to receive a relative template but got an absoulte one: #{other.inspect}. If you think this is a bug, please report it."
789
818
  end
790
819
 
791
- # Is this section right bounded?
792
- def right_bound?
793
- tokens.last.kind_of? RightBound
820
+ if other.pattern == ''
821
+ return self
794
822
  end
795
-
796
- # Concatenates this section with anything that can be coerced into a section.
797
- #
798
- # @example
799
- # sect = URITemplate::Draft7::Section.new('/prefix…')
800
- # sect >> '…/mid…' >> '…/end' #=> URITemplate::Draft7::Section.new('/prefix/mid/end')
801
- #
802
- # @example
803
- # sect = URITemplate::Draft7::Section.new('/prefix…')
804
- # sect >> '……' #=> sect
805
- #
806
- # @example Just ellipsis has a special syntax to allow empty matches.
807
- # sect = URITemplate::Draft7::Section.new('…')
808
- # combo = sect >> '…'
809
- # combo.pattern #=> ""
810
- # combo === "" #=> true
811
- # combo === "/foo" #=> false
812
- #
813
- # @return Section
814
- def >>(other)
815
- o = self.class.try_convert(other)
816
- if o.kind_of? Section
817
- if !self.right_bound?
818
- if o.pattern == ELLIPSIS
819
- return self.class.new("", o.options)
820
- elsif !o.left_bound?
821
- #if self.tokens.size == 1
822
- # return o
823
- #end
824
- tkns = self.tokens[0..-2] + o.tokens[1..-1]
825
- unless tkns.first.kind_of? Terminal
826
- tkns.unshift(LeftBound.new)
827
- end
828
- unless tkns.last.kind_of? Terminal
829
- tkns.push(RightBound.new)
830
- end
831
- return self.class.new(tkns, o.options)
823
+ # Merge!
824
+ # Analyze the last token of this an the first token of the next and try to merge them
825
+ if self.tokens.last.kind_of?(Literal)
826
+ if self.tokens.last.string[-1] == '/' # the last token ends with an /
827
+ if other.tokens.first.kind_of? Literal
828
+ # both seems to be paths, merge them!
829
+ if other.tokens.first.string[0] == '/'
830
+ # strip one '/'
831
+ return self.class.new( self.tokens[0..-2] + [ Literal.new(self.tokens.last.string + other.tokens.first.string[1..-1]) ] + other.tokens[1..-1] )
832
+ else
833
+ # no problem, but we can merge them
834
+ return self.class.new( self.tokens[0..-2] + [ Literal.new(self.tokens.last.string + other.tokens.first.string) ] + other.tokens[1..-1] )
832
835
  end
836
+ elsif other.tokens.first.kind_of? Expression::Path
837
+ # this will automatically insert '/'
838
+ # so we can strip one '/'
839
+ return self.class.new( self.tokens[0..-2] + [ Literal.new(self.tokens.last.string[0..-2]) ] + other.tokens )
833
840
  end
834
- raise ArgumentError, "Expected something that could be converted to a URITemplate section, but got #{other.inspect}"
835
841
  end
836
842
  end
843
+ if other.tokens.first.kind_of?(Expression::Path) or (other.tokens.first.kind_of?(Literal) and other.tokens.first.string[0] == '/')
844
+ return self.class.new( self.tokens + other.tokens )
845
+ else
846
+ return self.class.new( self.tokens + [Literal.new('/')] + other.tokens )
847
+ end
848
+ end
849
+
850
+
851
+ #
852
+ # should be relative:
853
+ # xxx ...
854
+ # {xxx}x ...
855
+ #
856
+ # should not be relative:
857
+ # {proto}:// ...
858
+ # http:// ...
859
+ # http{ssl}:// ...
860
+ #
861
+ def absolute?
862
+ read_chars = ""
837
863
 
838
- protected
839
- # @private
840
- def tokenize!
841
- pat = pattern
842
- if pat == ELLIPSIS # just ellipsis
843
- return [LeftBound.new, Open.new]
844
- else
845
- lb = (pat[0] != ELLIPSIS)
846
- rb = (pat[-1] != ELLIPSIS)
864
+ tokens.each do |token|
865
+ if token.kind_of? Expression
866
+ if token.class::OPERATOR == ''
867
+ read_chars << "x"
868
+ else
869
+ return false
870
+ end
871
+ elsif token.kind_of? Literal
872
+ read_chars << token.string
873
+ end
874
+ if read_chars =~ /^[a-z]+:\/\//i
875
+ return true
876
+ elsif read_chars =~ /(?<!:|\/)\/(?!\/)/
877
+ return false
847
878
  end
848
- pat = pat[ (lb ? 0 : 1)..(rb ? -1 : -2) ]
849
- [lb ? LeftBound.new : Open.new] + Tokenizer.new(pat).to_a + [rb ? RightBound.new : Open.new]
850
879
  end
851
-
880
+
881
+ return false
852
882
  end
853
883
 
854
- # Compares two template patterns.
855
- def ==(tpl)
856
- return false if self.class != tpl.class
857
- return self.pattern == tpl.pattern
884
+ # Returns the number of static characters in this template.
885
+ # This method is useful for routing, since it's often pointful to use the url with fewer variable characters.
886
+ # For example 'static' and 'sta{var}' both match 'static', but in most cases 'static' should be prefered over 'sta{var}' since it's more specific.
887
+ #
888
+ # @example
889
+ # URITemplate::Draft7.new('/xy/').static_characters #=> 4
890
+ # URITemplate::Draft7.new('{foo}').static_characters #=> 0
891
+ # URITemplate::Draft7.new('a{foo}b').static_characters #=> 2
892
+ #
893
+ # @return Numeric
894
+ def static_characters
895
+ @static_characters ||= tokens.select{|t| t.kind_of?(Literal) }.map{|t| t.string.size }.inject(0,:+)
858
896
  end
859
-
860
- # @method ===(uri)
861
- # Alias for to_r.=== . Tests whether this template matches a given uri.
862
- # @return TrueClass, FalseClass
863
- def_delegators :to_r, :===
864
-
865
- # @method match(uri)
866
- # Alias for to_r.match . Matches this template against the given uri.
867
- # @yield MatchData
868
- # @return MatchData, Object
869
- def_delegators :to_r, :match
870
897
 
871
898
  protected
872
899
  # @private
873
900
  def tokenize!
874
- [LeftBound.new] + Tokenizer.new(pattern).to_a + [RightBound.new]
901
+ Tokenizer.new(pattern).to_a
875
902
  end
876
903
 
877
904
  def tokens
data/uri_template.gemspec CHANGED
@@ -1,12 +1,12 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'uri_template'
3
- s.version = '0.0.2'
4
- s.date = '2011-11-01'
3
+ s.version = '0.1.0'
4
+ s.date = '2011-11-02'
5
5
  s.authors = ["HannesG"]
6
6
  s.email = %q{hannes.georg@googlemail.com}
7
7
  s.summary = 'A templating system for URIs.'
8
8
  s.homepage = 'http://github.com/hannesg/uri_template'
9
- s.description = 'A templating system for URIs, which implements http://tools.ietf.org/html/draft-gregorio-uritemplate-07 . An implementation of an older version of that spec is known as addressable. This system however is intended to be extended when newer specs evolve. For now only draft 7 is supported. Downside: only for 1.9 compatible since it uses Oniguruma regexp.'
9
+ s.description = 'A templating system for URIs, which implements http://tools.ietf.org/html/draft-gregorio-uritemplate-07 . An implementation of an older version of that spec is known as addressable. This gem however is intended to be extended when newer specs evolve. For now only draft 7 is supported. Downside: only for 1.9 compatible since it uses Oniguruma regexp.'
10
10
 
11
11
  s.require_paths = ['lib']
12
12
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uri_template
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-11-01 00:00:00.000000000Z
12
+ date: 2011-11-02 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &22417520 !ruby/object:Gem::Requirement
16
+ requirement: &14484700 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *22417520
24
+ version_requirements: *14484700
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: yard
27
- requirement: &22415680 !ruby/object:Gem::Requirement
27
+ requirement: &14482080 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,18 +32,19 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *22415680
35
+ version_requirements: *14482080
36
36
  description: ! 'A templating system for URIs, which implements http://tools.ietf.org/html/draft-gregorio-uritemplate-07
37
37
  . An implementation of an older version of that spec is known as addressable. This
38
- system however is intended to be extended when newer specs evolve. For now only
39
- draft 7 is supported. Downside: only for 1.9 compatible since it uses Oniguruma
40
- regexp.'
38
+ gem however is intended to be extended when newer specs evolve. For now only draft
39
+ 7 is supported. Downside: only for 1.9 compatible since it uses Oniguruma regexp.'
41
40
  email: hannes.georg@googlemail.com
42
41
  executables: []
43
42
  extensions: []
44
43
  extra_rdoc_files: []
45
44
  files:
46
45
  - lib/uri_template.rb
46
+ - lib/uri_template/colon.rb
47
+ - lib/uri_template/draft2.rb
47
48
  - lib/uri_template/draft7.rb
48
49
  - lib/uri_template/utils.rb
49
50
  - uri_template.gemspec