uri_template 0.4.0 → 0.5.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.md +53 -0
- data/README.md +56 -0
- data/lib/uri_template/colon.rb +88 -29
- data/lib/uri_template/expression.rb +33 -0
- data/lib/uri_template/literal.rb +53 -0
- data/lib/uri_template/rfc6570/expression/named.rb +72 -0
- data/lib/uri_template/rfc6570/expression/unnamed.rb +76 -0
- data/lib/uri_template/rfc6570/expression.rb +374 -0
- data/lib/uri_template/rfc6570/regex_builder.rb +117 -0
- data/lib/uri_template/rfc6570.rb +18 -536
- data/lib/uri_template/token.rb +64 -0
- data/lib/uri_template/utils.rb +47 -10
- data/lib/uri_template.rb +189 -88
- data/uri_template.gemspec +4 -4
- metadata +14 -10
- data/CHANGELOG +0 -46
- data/README +0 -67
- data/lib/uri_template/draft7.rb +0 -266
data/CHANGELOG.md
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# 0.5.0 - 23.09.2012
|
2
|
+
- - Removed draft7
|
3
|
+
- - splitted absoulte? method into host? and scheme?
|
4
|
+
- + the URITemplate interface is now much stronger
|
5
|
+
- * code quality _significantly_ improved
|
6
|
+
- + concat method
|
7
|
+
|
8
|
+
# 0.4.0 - 06.07.2012
|
9
|
+
- + expand now accepts symbols as keys ( thanks to @peterhellber )
|
10
|
+
- + expand now accepts arrays of pairs ( thanks to @peterhellber )
|
11
|
+
- * fixed some testing bugs
|
12
|
+
|
13
|
+
# 0.3.0 - 24.05.2012
|
14
|
+
- + Implemented the final version. Default implementation is now RFC 6570
|
15
|
+
- * BUGFIX: variables with terminal dots were allowed
|
16
|
+
- * BUGFIX: lists of commas were parsed incorrectly
|
17
|
+
|
18
|
+
# 0.2.1 - 30.12.2011
|
19
|
+
- * Compatibility: Works now with MRI 1.8.7 and REE
|
20
|
+
|
21
|
+
# 0.2.0 - 03.12.2011
|
22
|
+
- * Reworked the escaping mechanism
|
23
|
+
- + escape_utils can now be used to boost escape/unescape performance
|
24
|
+
|
25
|
+
# 0.1.4 - 19.11.2011
|
26
|
+
- * Compatiblity: Works now with MRI 1.9.3, Rubinius and JRuby
|
27
|
+
- * Various (significant!) performance improvements
|
28
|
+
|
29
|
+
# 0.1.3 - 15.11.2011
|
30
|
+
- * BUGFIX: Draft7./ now concatenates literals correctly
|
31
|
+
- * BUGFIX: Draft7.tokens is now public
|
32
|
+
|
33
|
+
# 0.1.2 - 10.11.2011
|
34
|
+
- + added a new template-type: Colon
|
35
|
+
this should allow (some day) to rails-like routing tables
|
36
|
+
- + made the tokens-method mandatory and added two interfaces for tokens.
|
37
|
+
this allows cross-type features like variable anaylisis
|
38
|
+
|
39
|
+
# 0.1.1 - 4.11.2011
|
40
|
+
- + added a bunch of useful helper methods
|
41
|
+
|
42
|
+
# 0.1.0 - 2.11.2011
|
43
|
+
- - Removed Sections. They made too many headaches.
|
44
|
+
- + Made draft7 template concatenateable. This should replace sections.
|
45
|
+
- * BUGFIX: multiline uris were matched
|
46
|
+
- * BUGFIX: variablenames were decoded when this was not appreciated
|
47
|
+
|
48
|
+
# 0.0.2 - 1.11.2011
|
49
|
+
- * BUGFIX: Concatenating empty sections no more leads to catch-all templates, when an emtpy template was appreciated.
|
50
|
+
- + The extracted variables now contains the keys :suffix and :prefix if the match didn't consume the whole uri.
|
51
|
+
|
52
|
+
# 0.0.1 - 30.10.2011
|
53
|
+
- Initial version
|
data/README.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
URITemplate - a uri template library
|
2
|
+
========================
|
3
|
+
|
4
|
+
[](http://travis-ci.org/hannesg/uri_template)
|
5
|
+
[](https://gemnasium.com/hannesg/uri_template)
|
6
|
+
[](https://codeclimate.com/github/hannesg/uri_template)
|
7
|
+
|
8
|
+
With URITemplate you can generate URIs based on simple templates and extract variables from URIs using the same templates. There are currently two syntaxes defined. Namely the one defined in [RFC 6570]( http://tools.ietf.org/html/rfc6570 ) and a colon based syntax, similiar to the one used by sinatra.
|
9
|
+
|
10
|
+
From version 0.2.0, it will use escape_utils if available. This will significantly boost uri-escape/unescape performance if more characters need to be escaped ( may be slightly slower in trivial cases. working on that ... ), but does not run everywhere. To enable this, do the following:
|
11
|
+
|
12
|
+
# escape_utils has to be loaded when uri_templates is loaded
|
13
|
+
gem 'escape_utils'
|
14
|
+
require 'escape_utils'
|
15
|
+
|
16
|
+
gem 'uri_template'
|
17
|
+
require 'uri_template'
|
18
|
+
|
19
|
+
UriTemplate::Utils.using_escape_utils? #=> true
|
20
|
+
|
21
|
+
|
22
|
+
Examples
|
23
|
+
-------------------
|
24
|
+
|
25
|
+
require 'uri_template'
|
26
|
+
|
27
|
+
tpl = URITemplate.new('http://{host}{/segments*}/{file}{.extensions*}')
|
28
|
+
|
29
|
+
# This will give: http://www.host.com/path/to/a/file.x.y
|
30
|
+
tpl.expand('host'=>'www.host.com','segments'=>['path','to','a'],'file'=>'file','extensions'=>['x','y'])
|
31
|
+
|
32
|
+
# This will give: { 'host'=>'www.host.com','segments'=>['path','to','a'],'file'=>'file','extensions'=>['x','y']}
|
33
|
+
tpl.extract('http://www.host.com/path/to/a/file.x.y')
|
34
|
+
|
35
|
+
# If you like colon templates more:
|
36
|
+
tpl2 = URITemplate.new(:colon, '/:x/y')
|
37
|
+
|
38
|
+
# This will give: {'x' => 'z'}
|
39
|
+
tpl2.extract('/z/y')
|
40
|
+
|
41
|
+
|
42
|
+
RFC 6570 Syntax
|
43
|
+
--------------------
|
44
|
+
|
45
|
+
The syntax defined by [RFC 6570]( http://tools.ietf.org/html/rfc6570 ) is pretty straight forward. Basically anything surrounded by curly brackets is interpreted as variable.
|
46
|
+
|
47
|
+
URITemplate.new('{variable}').expand('variable' => 'value') #=> "value"
|
48
|
+
|
49
|
+
The way variables are inserted can be modified using operators. The operator is the first character between the curly brackets. There are seven operators defined `#`, `+`, `;`, `?`, `&`, `/` and `.`. So if you want to create a form-style query do this:
|
50
|
+
|
51
|
+
URITemplate.new('{?variable}').expand('variable' => 'value') #=> "?variable=value"
|
52
|
+
|
53
|
+
Benchmarks
|
54
|
+
-----------------------
|
55
|
+
|
56
|
+
I have assembled one benchmark based on the uritemplate-test examples. You can find them in the "benchmarks" folder. The short result: uri_template is 2-10x faster than addressable on ruby 1.9.3.
|
data/lib/uri_template/colon.rb
CHANGED
@@ -20,15 +20,45 @@ require 'forwardable'
|
|
20
20
|
require 'uri_template'
|
21
21
|
require 'uri_template/utils'
|
22
22
|
|
23
|
-
# A colon based template denotes variables with a colon.
|
24
|
-
# This template type is realy basic but having just on template type was a bit weird.
|
25
23
|
module URITemplate
|
26
24
|
|
25
|
+
# A colon based template denotes variables with a colon.
|
26
|
+
#
|
27
|
+
# This template type is somewhat compatible with sinatra.
|
28
|
+
#
|
29
|
+
# @example
|
30
|
+
# tpl = URITemplate::Colon.new('/foo/:bar')
|
31
|
+
# tpl.extract('/foo/baz') #=> {'bar'=>'baz'}
|
32
|
+
# tpl.expand('bar'=>'boom') #=> '/foo/boom'
|
33
|
+
#
|
27
34
|
class Colon
|
28
35
|
|
29
36
|
include URITemplate
|
30
37
|
|
31
|
-
VAR = /(?:\{:([a-z]+)\}|:([a-z]+)(?![a-z]))/u
|
38
|
+
VAR = /(?:\{:([a-z]+)\}|:([a-z]+)(?![a-z])|\*)/u
|
39
|
+
|
40
|
+
class InvalidValue < StandardError
|
41
|
+
|
42
|
+
include URITemplate::InvalidValue
|
43
|
+
|
44
|
+
attr_reader :variable, :value
|
45
|
+
|
46
|
+
def initialize(variable, value)
|
47
|
+
@variable = variable
|
48
|
+
@value = value
|
49
|
+
super(generate_message())
|
50
|
+
end
|
51
|
+
|
52
|
+
class SplatIsNotAnArray < self
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
|
57
|
+
def generate_message()
|
58
|
+
return "The template variable " + variable.inspect + " cannot expand the given value "+ value.inspect
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
32
62
|
|
33
63
|
class Token
|
34
64
|
|
@@ -44,11 +74,41 @@ class Colon
|
|
44
74
|
end
|
45
75
|
|
46
76
|
def expand(vars)
|
47
|
-
return Utils.escape_url(Utils.object_to_param(vars[
|
77
|
+
return Utils.escape_url(Utils.object_to_param(vars[name]))
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_r
|
81
|
+
return '([^/]*?)'
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_s
|
85
|
+
return ":#{name}"
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
class Splat < Variable
|
91
|
+
|
92
|
+
SPLAT = 'splat'.freeze
|
93
|
+
|
94
|
+
attr_reader :index
|
95
|
+
|
96
|
+
def initialize(index)
|
97
|
+
@index = index
|
98
|
+
super(SPLAT)
|
99
|
+
end
|
100
|
+
|
101
|
+
def expand(vars)
|
102
|
+
var = vars[name]
|
103
|
+
if Array === var
|
104
|
+
return Utils.escape_uri(Utils.object_to_param(var[index]))
|
105
|
+
else
|
106
|
+
raise InvalidValue::SplatIsNotAnArray.new(name,var)
|
107
|
+
end
|
48
108
|
end
|
49
109
|
|
50
110
|
def to_r
|
51
|
-
return
|
111
|
+
return '(.+?)'
|
52
112
|
end
|
53
113
|
|
54
114
|
end
|
@@ -78,13 +138,13 @@ class Colon
|
|
78
138
|
# Tries to convert the value into a colon-template.
|
79
139
|
# @example
|
80
140
|
# URITemplate::Colon.try_convert('/foo/:bar/').pattern #=> '/foo/:bar/'
|
81
|
-
# URITemplate::Colon.try_convert(URITemplate
|
141
|
+
# URITemplate::Colon.try_convert(URITemplate.new(:rfc6570, '/foo/{bar}/')).pattern #=> '/foo/{:bar}/'
|
82
142
|
def self.try_convert(x)
|
83
143
|
if x.kind_of? String
|
84
144
|
return new(x)
|
85
145
|
elsif x.kind_of? self
|
86
146
|
return x
|
87
|
-
elsif x.kind_of? URITemplate::
|
147
|
+
elsif x.kind_of? URITemplate::RFC6570 and x.level == 1
|
88
148
|
return new( x.pattern.gsub(/\{(.*?)\}/u){ "{:#{$1}}" } )
|
89
149
|
else
|
90
150
|
return nil
|
@@ -92,6 +152,7 @@ class Colon
|
|
92
152
|
end
|
93
153
|
|
94
154
|
def initialize(pattern)
|
155
|
+
raise ArgumentError,"Expected a String but got #{pattern.inspect}" unless pattern.kind_of? String
|
95
156
|
@pattern = pattern
|
96
157
|
end
|
97
158
|
|
@@ -102,9 +163,20 @@ class Colon
|
|
102
163
|
def extract(uri)
|
103
164
|
md = self.to_r.match(uri)
|
104
165
|
return nil unless md
|
105
|
-
|
106
|
-
|
107
|
-
}.
|
166
|
+
result = {}
|
167
|
+
splat = []
|
168
|
+
self.tokens.select{|tk| tk.kind_of? URITemplate::Expression }.each_with_index do |tk,i|
|
169
|
+
if tk.kind_of? Token::Splat
|
170
|
+
splat << md[i+1]
|
171
|
+
result['splat'] = splat unless result.key? 'splat'
|
172
|
+
else
|
173
|
+
result[tk.name] = Utils.unescape_url( md[i+1] )
|
174
|
+
end
|
175
|
+
end
|
176
|
+
if block_given?
|
177
|
+
return yield(result)
|
178
|
+
end
|
179
|
+
return result
|
108
180
|
end
|
109
181
|
|
110
182
|
def type
|
@@ -119,30 +191,17 @@ class Colon
|
|
119
191
|
@tokens ||= tokenize!
|
120
192
|
end
|
121
193
|
|
122
|
-
# Tries to concatenate two templates, as if they were path segments.
|
123
|
-
# Removes double slashes or inserts one if they are missing.
|
124
|
-
#
|
125
|
-
# @example
|
126
|
-
# tpl = URITemplate::Colon.new('/xy/')
|
127
|
-
# (tpl / '/z/' ).pattern #=> '/xy/z/'
|
128
|
-
# (tpl / 'z/' ).pattern #=> '/xy/z/'
|
129
|
-
# (tpl / ':z' ).pattern #=> '/xy/:z'
|
130
|
-
# (tpl / ':a' / 'b' ).pattern #=> '/xy/:a/b'
|
131
|
-
#
|
132
|
-
def /(o)
|
133
|
-
this, other, this_converted, other_converted = URITemplate.coerce( self, o )
|
134
|
-
if this_converted
|
135
|
-
return this / other
|
136
|
-
end
|
137
|
-
return self.class.new( File.join( this.pattern, other.pattern ) )
|
138
|
-
end
|
139
|
-
|
140
194
|
protected
|
141
195
|
|
142
196
|
def tokenize!
|
197
|
+
number_of_splats = 0
|
143
198
|
RegexpEnumerator.new(VAR).each(@pattern).map{|x|
|
144
199
|
if x.kind_of? String
|
145
|
-
Token::Static.new(x)
|
200
|
+
Token::Static.new(Utils.escape_uri(x))
|
201
|
+
elsif x[0] == '*'
|
202
|
+
n = number_of_splats
|
203
|
+
number_of_splats = number_of_splats + 1
|
204
|
+
Token::Splat.new(n)
|
146
205
|
else
|
147
206
|
# TODO: when rubinius supports ambigious names this could be replaced with x['name'] *sigh*
|
148
207
|
Token::Variable.new(x[1] || x[2])
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
# This program is free software: you can redistribute it and/or modify
|
3
|
+
# it under the terms of the Affero GNU General Public License as published by
|
4
|
+
# the Free Software Foundation, either version 3 of the License, or
|
5
|
+
# (at your option) any later version.
|
6
|
+
#
|
7
|
+
# This program is distributed in the hope that it will be useful,
|
8
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
9
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
10
|
+
# GNU General Public License for more details.
|
11
|
+
#
|
12
|
+
# You should have received a copy of the GNU General Public License
|
13
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
14
|
+
#
|
15
|
+
# (c) 2011 - 2012 by Hannes Georg
|
16
|
+
#
|
17
|
+
|
18
|
+
# A module which all non-literal tokens should include.
|
19
|
+
module URITemplate::Expression
|
20
|
+
|
21
|
+
include URITemplate::Token
|
22
|
+
|
23
|
+
attr_reader :variables
|
24
|
+
|
25
|
+
def literal?
|
26
|
+
false
|
27
|
+
end
|
28
|
+
|
29
|
+
def expression?
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
# This program is free software: you can redistribute it and/or modify
|
3
|
+
# it under the terms of the Affero GNU General Public License as published by
|
4
|
+
# the Free Software Foundation, either version 3 of the License, or
|
5
|
+
# (at your option) any later version.
|
6
|
+
#
|
7
|
+
# This program is distributed in the hope that it will be useful,
|
8
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
9
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
10
|
+
# GNU General Public License for more details.
|
11
|
+
#
|
12
|
+
# You should have received a copy of the GNU General Public License
|
13
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
14
|
+
#
|
15
|
+
# (c) 2011 - 2012 by Hannes Georg
|
16
|
+
#
|
17
|
+
|
18
|
+
# A module which all literal tokens should include.
|
19
|
+
module URITemplate::Literal
|
20
|
+
|
21
|
+
include URITemplate::Token
|
22
|
+
|
23
|
+
SLASH = ?/
|
24
|
+
|
25
|
+
attr_reader :string
|
26
|
+
|
27
|
+
def literal?
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
def expression?
|
32
|
+
false
|
33
|
+
end
|
34
|
+
|
35
|
+
def size
|
36
|
+
0
|
37
|
+
end
|
38
|
+
|
39
|
+
def expand(_)
|
40
|
+
return string
|
41
|
+
end
|
42
|
+
|
43
|
+
def starts_with_slash?
|
44
|
+
string[0] == SLASH
|
45
|
+
end
|
46
|
+
|
47
|
+
def ends_with_slash?
|
48
|
+
string[-1] == SLASH
|
49
|
+
end
|
50
|
+
|
51
|
+
alias to_s string
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
# This program is free software: you can redistribute it and/or modify
|
3
|
+
# it under the terms of the Affero GNU General Public License as published by
|
4
|
+
# the Free Software Foundation, either version 3 of the License, or
|
5
|
+
# (at your option) any later version.
|
6
|
+
#
|
7
|
+
# This program is distributed in the hope that it will be useful,
|
8
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
9
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
10
|
+
# GNU General Public License for more details.
|
11
|
+
#
|
12
|
+
# You should have received a copy of the GNU General Public License
|
13
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
14
|
+
#
|
15
|
+
# (c) 2011 - 2012 by Hannes Georg
|
16
|
+
#
|
17
|
+
|
18
|
+
require 'uri_template/rfc6570'
|
19
|
+
|
20
|
+
class URITemplate::RFC6570::Expression::Named < URITemplate::RFC6570::Expression
|
21
|
+
|
22
|
+
alias self_pair pair
|
23
|
+
|
24
|
+
def to_r_source
|
25
|
+
source = regex_builder
|
26
|
+
source.group do
|
27
|
+
source.escaped_prefix
|
28
|
+
first = true
|
29
|
+
@variable_specs.each do | var, expand , max_length |
|
30
|
+
if expand
|
31
|
+
source.capture do
|
32
|
+
source.separated_list(first) do
|
33
|
+
source.character_class('+')\
|
34
|
+
.escaped_pair_connector\
|
35
|
+
.character_class_with_comma(max_length)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
else
|
39
|
+
source.group do
|
40
|
+
source.escaped_separator unless first
|
41
|
+
source << Regexp.escape(var)
|
42
|
+
source.group do
|
43
|
+
source.escaped_pair_connector
|
44
|
+
source.capture do
|
45
|
+
source.character_class_with_comma(max_length)
|
46
|
+
end
|
47
|
+
source << '|' unless self.class::PAIR_IF_EMPTY
|
48
|
+
end
|
49
|
+
end.length('?')
|
50
|
+
end
|
51
|
+
first = false
|
52
|
+
end
|
53
|
+
end.length('?')
|
54
|
+
return source.join
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def extracted_nil
|
60
|
+
self.class::PAIR_IF_EMPTY ? nil : ""
|
61
|
+
end
|
62
|
+
|
63
|
+
def after_expand(name, splitted)
|
64
|
+
result = URITemplate::Utils.pair_array_to_hash2( splitted )
|
65
|
+
if result.size == 1 && result[0][0] == name
|
66
|
+
return result
|
67
|
+
else
|
68
|
+
return [ [ name , result ] ]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
# This program is free software: you can redistribute it and/or modify
|
3
|
+
# it under the terms of the Affero GNU General Public License as published by
|
4
|
+
# the Free Software Foundation, either version 3 of the License, or
|
5
|
+
# (at your option) any later version.
|
6
|
+
#
|
7
|
+
# This program is distributed in the hope that it will be useful,
|
8
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
9
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
10
|
+
# GNU General Public License for more details.
|
11
|
+
#
|
12
|
+
# You should have received a copy of the GNU General Public License
|
13
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
14
|
+
#
|
15
|
+
# (c) 2011 - 2012 by Hannes Georg
|
16
|
+
#
|
17
|
+
|
18
|
+
require 'uri_template/rfc6570'
|
19
|
+
|
20
|
+
class URITemplate::RFC6570::Expression::Unnamed < URITemplate::RFC6570::Expression
|
21
|
+
|
22
|
+
def self_pair(_, value, max_length = 0,&block)
|
23
|
+
if block
|
24
|
+
ev = value.map(&block).join(self.class::LIST_CONNECTOR)
|
25
|
+
else
|
26
|
+
ev = escape(value)
|
27
|
+
end
|
28
|
+
cut( ev, max_length ,&block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_r_source
|
32
|
+
vs = @variable_specs.size - 1
|
33
|
+
i = 0
|
34
|
+
source = regex_builder
|
35
|
+
source.group do
|
36
|
+
source.escaped_prefix
|
37
|
+
@variable_specs.each do | var, expand , max_length |
|
38
|
+
last = (vs == i)
|
39
|
+
first = (i == 0)
|
40
|
+
if expand
|
41
|
+
source.group(true) do
|
42
|
+
source.separated_list(first) do
|
43
|
+
source.group do
|
44
|
+
source.character_class('+').reluctant
|
45
|
+
source.escaped_pair_connector
|
46
|
+
end.length('?')
|
47
|
+
source.character_class(max_length)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
else
|
51
|
+
source.escaped_separator unless first
|
52
|
+
source.group(true) do
|
53
|
+
if last
|
54
|
+
source.character_class_with_comma(max_length)
|
55
|
+
else
|
56
|
+
source.character_class(max_length)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
i = i+1
|
61
|
+
end
|
62
|
+
end.length('?')
|
63
|
+
return source.join
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def after_expand(name, splitted)
|
69
|
+
if splitted.none?{|_,b| b }
|
70
|
+
return [ [ name, splitted.map{|a,_| a } ] ]
|
71
|
+
else
|
72
|
+
return [ [ name, splitted ] ]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|