utopia 0.9.17
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/ext/utopia/xnode/fast_scanner/extconf.rb +6 -0
- data/ext/utopia/xnode/fast_scanner/parser.c +289 -0
- data/lib/utopia.rb +2 -0
- data/lib/utopia/etanni.rb +96 -0
- data/lib/utopia/extensions.rb +87 -0
- data/lib/utopia/link.rb +243 -0
- data/lib/utopia/middleware.rb +33 -0
- data/lib/utopia/middleware/all.rb +24 -0
- data/lib/utopia/middleware/benchmark.rb +47 -0
- data/lib/utopia/middleware/content.rb +139 -0
- data/lib/utopia/middleware/content/node.rb +363 -0
- data/lib/utopia/middleware/controller.rb +198 -0
- data/lib/utopia/middleware/directory_index.rb +54 -0
- data/lib/utopia/middleware/localization.rb +94 -0
- data/lib/utopia/middleware/localization/name.rb +64 -0
- data/lib/utopia/middleware/logger.rb +68 -0
- data/lib/utopia/middleware/redirector.rb +171 -0
- data/lib/utopia/middleware/requester.rb +116 -0
- data/lib/utopia/middleware/static.rb +186 -0
- data/lib/utopia/path.rb +193 -0
- data/lib/utopia/response_helper.rb +22 -0
- data/lib/utopia/session/encrypted_cookie.rb +115 -0
- data/lib/utopia/tag.rb +84 -0
- data/lib/utopia/tags.rb +32 -0
- data/lib/utopia/tags/all.rb +25 -0
- data/lib/utopia/tags/env.rb +24 -0
- data/lib/utopia/tags/fortune.rb +20 -0
- data/lib/utopia/tags/gallery.rb +175 -0
- data/lib/utopia/tags/google_analytics.rb +37 -0
- data/lib/utopia/tags/node.rb +24 -0
- data/lib/utopia/tags/override.rb +28 -0
- data/lib/utopia/time_store.rb +102 -0
- data/lib/utopia/version.rb +24 -0
- data/lib/utopia/xnode.rb +17 -0
- data/lib/utopia/xnode/processor.rb +97 -0
- data/lib/utopia/xnode/scanner.rb +153 -0
- metadata +168 -0
@@ -0,0 +1,289 @@
|
|
1
|
+
#include "ruby.h"
|
2
|
+
|
3
|
+
#define OPENED_TAG 0
|
4
|
+
#define CLOSED_TAG 1
|
5
|
+
|
6
|
+
static VALUE rb_XNode;
|
7
|
+
static VALUE rb_XNode_ScanError;
|
8
|
+
static VALUE rb_XNode_Scanner;
|
9
|
+
|
10
|
+
static ID rb_XNode_CDATA_Callback;
|
11
|
+
static ID rb_XNode_BeginTag_Callback;
|
12
|
+
static ID rb_XNode_FinishTag_Callback;
|
13
|
+
static ID rb_XNode_Attribute_Callback;
|
14
|
+
static ID rb_XNode_Comment_Callback;
|
15
|
+
static ID rb_XNode_Instruction_Callback;
|
16
|
+
static ID rb_XNode_Comment_Callback;
|
17
|
+
|
18
|
+
#define NewObject(type) (type*)malloc(sizeof(type))
|
19
|
+
|
20
|
+
typedef struct {
|
21
|
+
VALUE delegate;
|
22
|
+
VALUE content;
|
23
|
+
} XNode_Scanner;
|
24
|
+
|
25
|
+
typedef char Character;
|
26
|
+
typedef Character * Iterator;
|
27
|
+
|
28
|
+
static void XNode_Scanner_Mark(XNode_Scanner * scanner) {
|
29
|
+
rb_gc_mark(scanner->delegate);
|
30
|
+
rb_gc_mark(scanner->content);
|
31
|
+
}
|
32
|
+
|
33
|
+
static void XNode_Scanner_Free(XNode_Scanner * scanner) {
|
34
|
+
free(scanner);
|
35
|
+
}
|
36
|
+
|
37
|
+
static VALUE XNode_Scanner_Allocate(VALUE klass) {
|
38
|
+
XNode_Scanner * scanner = NewObject(XNode_Scanner);
|
39
|
+
|
40
|
+
return Data_Wrap_Struct(klass, XNode_Scanner_Mark, XNode_Scanner_Free, scanner);
|
41
|
+
}
|
42
|
+
|
43
|
+
static VALUE XNode_Scanner_Initialize(VALUE self, VALUE delegate, VALUE content) {
|
44
|
+
Check_Type(content, T_STRING);
|
45
|
+
|
46
|
+
XNode_Scanner * scanner;
|
47
|
+
|
48
|
+
Data_Get_Struct(self, XNode_Scanner, scanner);
|
49
|
+
|
50
|
+
scanner->delegate = delegate;
|
51
|
+
scanner->content = content;
|
52
|
+
|
53
|
+
return Qnil;
|
54
|
+
}
|
55
|
+
|
56
|
+
static VALUE rb_str_from_iterators(Iterator start, Iterator end) {
|
57
|
+
return rb_str_new(start, end - start);
|
58
|
+
}
|
59
|
+
|
60
|
+
static int is_whitespace(Iterator i) {
|
61
|
+
return (*i == ' ' || *i == '\r' || *i == '\n' || *i == '\t');
|
62
|
+
}
|
63
|
+
|
64
|
+
static int is_tag_character(Iterator i) {
|
65
|
+
return (*i == '<' || *i == '>' || *i == '/');
|
66
|
+
}
|
67
|
+
|
68
|
+
static int is_tag_name(Iterator i) {
|
69
|
+
return !(is_whitespace(i) || is_tag_character(i));
|
70
|
+
}
|
71
|
+
|
72
|
+
static Iterator expect_character(XNode_Scanner * scanner, Iterator start, Iterator end, Character c) {
|
73
|
+
if (start >= end || *start != c) {
|
74
|
+
VALUE message = rb_str_new2("Expected Character ");
|
75
|
+
rb_str_cat(message, &c, 1);
|
76
|
+
VALUE exception = rb_exc_new3(rb_XNode_ScanError, message);
|
77
|
+
rb_exc_raise(exception);
|
78
|
+
}
|
79
|
+
|
80
|
+
return start + 1;
|
81
|
+
}
|
82
|
+
|
83
|
+
static Iterator skip_whitespace(Iterator start, Iterator end) {
|
84
|
+
while (start < end) {
|
85
|
+
if (!is_whitespace(start))
|
86
|
+
break;
|
87
|
+
|
88
|
+
++start;
|
89
|
+
}
|
90
|
+
|
91
|
+
return start;
|
92
|
+
}
|
93
|
+
|
94
|
+
static Iterator XNode_Scanner_Parse_CDATA(XNode_Scanner * scanner, Iterator start, Iterator end) {
|
95
|
+
Iterator cdata_start = start;
|
96
|
+
|
97
|
+
while (start < end && *start != '<') {
|
98
|
+
++start;
|
99
|
+
}
|
100
|
+
|
101
|
+
Iterator cdata_end = start;
|
102
|
+
|
103
|
+
if (cdata_start != cdata_end) {
|
104
|
+
VALUE cdata = rb_str_from_iterators(cdata_start, cdata_end);
|
105
|
+
rb_funcall(scanner->delegate, rb_XNode_CDATA_Callback, 1, cdata);
|
106
|
+
}
|
107
|
+
|
108
|
+
return start;
|
109
|
+
}
|
110
|
+
|
111
|
+
static Iterator XNode_Scanner_Parse_Attributes(XNode_Scanner * scanner, Iterator start, Iterator end) {
|
112
|
+
while (start < end && !is_tag_character(start)) {
|
113
|
+
start = skip_whitespace(start, end);
|
114
|
+
|
115
|
+
Iterator attribute_name_start = start;
|
116
|
+
|
117
|
+
while (start < end && *start != '=') {
|
118
|
+
++start;
|
119
|
+
}
|
120
|
+
|
121
|
+
Iterator attribute_name_end = start;
|
122
|
+
|
123
|
+
start = expect_character(scanner, start, end, '=');
|
124
|
+
start = expect_character(scanner, start, end, '"');
|
125
|
+
|
126
|
+
Iterator attribute_value_start = start;
|
127
|
+
|
128
|
+
while (start < end && *start != '"') {
|
129
|
+
++start;
|
130
|
+
}
|
131
|
+
|
132
|
+
Iterator attribute_value_end = start;
|
133
|
+
start = expect_character(scanner, start, end, '"');
|
134
|
+
|
135
|
+
VALUE attribute_name = rb_str_from_iterators(attribute_name_start, attribute_name_end);
|
136
|
+
VALUE attribute_value = rb_str_from_iterators(attribute_value_start, attribute_value_end);
|
137
|
+
rb_funcall(scanner->delegate, rb_XNode_Attribute_Callback, 2, attribute_name, attribute_value);
|
138
|
+
|
139
|
+
start = skip_whitespace(start, end);
|
140
|
+
}
|
141
|
+
|
142
|
+
return start;
|
143
|
+
}
|
144
|
+
|
145
|
+
static Iterator XNode_Scanner_Parse_Tag_Normal(XNode_Scanner * scanner, Iterator start, Iterator end, int begin_tag_type) {
|
146
|
+
Iterator tag_name_start = start;
|
147
|
+
int finish_tag_type;
|
148
|
+
|
149
|
+
while (start < end && is_tag_name(start)) {
|
150
|
+
++start;
|
151
|
+
}
|
152
|
+
|
153
|
+
Iterator tag_name_end = start;
|
154
|
+
|
155
|
+
VALUE tag_name = rb_str_from_iterators(tag_name_start, tag_name_end);
|
156
|
+
rb_funcall(scanner->delegate, rb_XNode_BeginTag_Callback, 2, tag_name, INT2FIX(begin_tag_type));
|
157
|
+
|
158
|
+
start = skip_whitespace(start, end);
|
159
|
+
|
160
|
+
if (!is_tag_character(start))
|
161
|
+
start = XNode_Scanner_Parse_Attributes(scanner, start, end);
|
162
|
+
|
163
|
+
if (*start == '/') {
|
164
|
+
if (begin_tag_type == CLOSED_TAG) {
|
165
|
+
VALUE exception = rb_exc_new2(rb_XNode_ScanError, "Tag cannot be closed at both ends!");
|
166
|
+
rb_exc_raise(exception);
|
167
|
+
}
|
168
|
+
|
169
|
+
finish_tag_type = CLOSED_TAG;
|
170
|
+
start += 2;
|
171
|
+
} else if (*start == '>') {
|
172
|
+
finish_tag_type = OPENED_TAG;
|
173
|
+
++start;
|
174
|
+
}
|
175
|
+
|
176
|
+
rb_funcall(scanner->delegate, rb_XNode_FinishTag_Callback, 2, INT2FIX(begin_tag_type), INT2FIX(finish_tag_type));
|
177
|
+
|
178
|
+
return start;
|
179
|
+
}
|
180
|
+
|
181
|
+
static Iterator XNode_Scanner_Parse_Tag_Comment(XNode_Scanner * scanner, Iterator start, Iterator end) {
|
182
|
+
Iterator comment_start = start;
|
183
|
+
|
184
|
+
while (start < end && *start != '>') {
|
185
|
+
++start;
|
186
|
+
}
|
187
|
+
|
188
|
+
Iterator comment_end = start;
|
189
|
+
|
190
|
+
start = expect_character(scanner, start, end, '>');
|
191
|
+
|
192
|
+
VALUE comment = rb_str_from_iterators(comment_start, comment_end);
|
193
|
+
rb_funcall(scanner->delegate, rb_XNode_Comment_Callback, 1, comment);
|
194
|
+
|
195
|
+
return start;
|
196
|
+
}
|
197
|
+
|
198
|
+
static Iterator XNode_Scanner_Parse_Tag_Instruction(XNode_Scanner * scanner, Iterator start, Iterator end) {
|
199
|
+
Iterator instruction_start = start;
|
200
|
+
|
201
|
+
while ((start+1) < end && *start != '?' && *(start+1) != '>') {
|
202
|
+
++start;
|
203
|
+
}
|
204
|
+
|
205
|
+
Iterator instruction_end = start;
|
206
|
+
|
207
|
+
start = expect_character(scanner, start, end, '?');
|
208
|
+
start = expect_character(scanner, start, end, '>');
|
209
|
+
|
210
|
+
VALUE instruction = rb_str_from_iterators(instruction_start, instruction_end);
|
211
|
+
rb_funcall(scanner->delegate, rb_XNode_Instruction_Callback, 1, instruction);
|
212
|
+
|
213
|
+
return start;
|
214
|
+
}
|
215
|
+
|
216
|
+
static Iterator XNode_Scanner_Parse_Tag(XNode_Scanner * scanner, Iterator start, Iterator end) {
|
217
|
+
if (*start == '<') {
|
218
|
+
++start;
|
219
|
+
|
220
|
+
if (*start == '/') {
|
221
|
+
++start;
|
222
|
+
start = XNode_Scanner_Parse_Tag_Normal(scanner, start, end, CLOSED_TAG);
|
223
|
+
} else if (*start == '!') {
|
224
|
+
++start;
|
225
|
+
start = XNode_Scanner_Parse_Tag_Comment(scanner, start, end);
|
226
|
+
} else if (*start == '?') {
|
227
|
+
++start;
|
228
|
+
start = XNode_Scanner_Parse_Tag_Instruction(scanner, start, end);
|
229
|
+
} else {
|
230
|
+
start = XNode_Scanner_Parse_Tag_Normal(scanner, start, end, OPENED_TAG);
|
231
|
+
}
|
232
|
+
}
|
233
|
+
|
234
|
+
return start;
|
235
|
+
}
|
236
|
+
|
237
|
+
static Iterator XNode_Scanner_Parse_Document(XNode_Scanner * scanner) {
|
238
|
+
Iterator current, start, end;
|
239
|
+
|
240
|
+
start = RSTRING(scanner->content)->ptr;
|
241
|
+
end = start + RSTRING(scanner->content)->len;
|
242
|
+
|
243
|
+
while (start < end) {
|
244
|
+
current = start;
|
245
|
+
|
246
|
+
current = XNode_Scanner_Parse_CDATA(scanner, current, end);
|
247
|
+
current = XNode_Scanner_Parse_Tag(scanner, current, end);
|
248
|
+
|
249
|
+
if (current == start) {
|
250
|
+
/* We did not parse anything! */
|
251
|
+
VALUE message = rb_str_new2("Parser Stuck at ");
|
252
|
+
|
253
|
+
int len = 10;
|
254
|
+
if (current + len > end)
|
255
|
+
len = end - current;
|
256
|
+
|
257
|
+
rb_str_cat(message, current, len);
|
258
|
+
VALUE exception = rb_exc_new3(rb_XNode_ScanError, message);
|
259
|
+
rb_exc_raise(exception);
|
260
|
+
}
|
261
|
+
|
262
|
+
start = current;
|
263
|
+
}
|
264
|
+
}
|
265
|
+
|
266
|
+
static VALUE XNode_Scanner_Parse(VALUE self) {
|
267
|
+
XNode_Scanner * scanner;
|
268
|
+
|
269
|
+
Data_Get_Struct(self, XNode_Scanner, scanner);
|
270
|
+
|
271
|
+
XNode_Scanner_Parse_Document(scanner);
|
272
|
+
}
|
273
|
+
|
274
|
+
void Init_xnode() {
|
275
|
+
rb_XNode = rb_define_module("XNode");
|
276
|
+
rb_XNode_ScanError = rb_define_class_under(rb_XNode, "ScanError", rb_eStandardError);
|
277
|
+
rb_XNode_Scanner = rb_define_class_under(rb_XNode, "Scanner", rb_cObject);
|
278
|
+
|
279
|
+
rb_define_alloc_func(rb_XNode_Scanner, XNode_Scanner_Allocate);
|
280
|
+
rb_define_method(rb_XNode_Scanner, "initialize", XNode_Scanner_Initialize, 2);
|
281
|
+
rb_define_method(rb_XNode_Scanner, "parse", XNode_Scanner_Parse, 0);
|
282
|
+
|
283
|
+
rb_XNode_CDATA_Callback = rb_intern("cdata");
|
284
|
+
rb_XNode_BeginTag_Callback = rb_intern("begin_tag");
|
285
|
+
rb_XNode_FinishTag_Callback = rb_intern("finish_tag");
|
286
|
+
rb_XNode_Attribute_Callback = rb_intern("attribute");
|
287
|
+
rb_XNode_Comment_Callback = rb_intern("comment");
|
288
|
+
rb_XNode_Instruction_Callback = rb_intern("instruction");
|
289
|
+
}
|
data/lib/utopia.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# Copyright (c) 2010 Samuel Williams. Released under the GNU GPLv3.
|
2
|
+
#
|
3
|
+
# This program is free software: you can redistribute it and/or modify
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
6
|
+
# (at your option) any later version.
|
7
|
+
#
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
+
# GNU General Public License for more details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU General Public License
|
14
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
# [Etanni] Copyright (c) 2008 Michael Fellinger <m.fellinger@gmail.com>
|
17
|
+
#
|
18
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
19
|
+
# of this software and associated documentation files (the "Software"), to
|
20
|
+
# deal in the Software without restriction, including without limitation the
|
21
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
22
|
+
# sell copies of the Software, and to permit persons to whom the Software is
|
23
|
+
# furnished to do so, subject to the following conditions:
|
24
|
+
#
|
25
|
+
# The above copyright notice and this permission notice shall be included in
|
26
|
+
# all copies or substantial portions of the Software.
|
27
|
+
#
|
28
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
29
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
30
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
31
|
+
# THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
32
|
+
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
33
|
+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
34
|
+
|
35
|
+
require 'digest/md5'
|
36
|
+
|
37
|
+
module Utopia
|
38
|
+
|
39
|
+
class Etanni
|
40
|
+
SEPARATOR = Digest::MD5.hexdigest(Time.new.to_s)
|
41
|
+
START = "\n_out_ << <<#{SEPARATOR}.chomp!\n"
|
42
|
+
STOP = "\n#{SEPARATOR}\n"
|
43
|
+
REPLACEMENT = "#{STOP}\\1#{START}"
|
44
|
+
|
45
|
+
def initialize(template, compiled = false)
|
46
|
+
if compiled
|
47
|
+
@compiled = template
|
48
|
+
else
|
49
|
+
@template = template
|
50
|
+
compile!
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def compile!
|
55
|
+
temp = @template.dup
|
56
|
+
temp.strip!
|
57
|
+
temp.gsub!(/<\?r\s+(.*?)\s+\?>/m, REPLACEMENT)
|
58
|
+
@compiled = "_out_ = [<<#{SEPARATOR}.chomp!]\n#{temp}#{STOP}_out_"
|
59
|
+
end
|
60
|
+
|
61
|
+
def result(binding, filename = '<Etanni>')
|
62
|
+
eval(@compiled, binding, filename).join
|
63
|
+
end
|
64
|
+
|
65
|
+
attr :compiled
|
66
|
+
end
|
67
|
+
|
68
|
+
class TemplateCache
|
69
|
+
CACHE_PREFIX = ".cache."
|
70
|
+
CACHE_ENABLED = true
|
71
|
+
|
72
|
+
def self.cache_path(path)
|
73
|
+
File.join(File.dirname(path), CACHE_PREFIX + File.basename(path))
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.mtime(path)
|
77
|
+
File.symlink?(path) ? File.lstat(file_name).mtime : File.mtime(path)
|
78
|
+
end
|
79
|
+
|
80
|
+
def initialize(path, template_class = Etanni)
|
81
|
+
@path = path
|
82
|
+
@cache_path = TemplateCache.cache_path(@path)
|
83
|
+
|
84
|
+
if !File.exist?(@cache_path) || (TemplateCache.mtime(@path) > TemplateCache.mtime(@cache_path))
|
85
|
+
@template = template_class.new(File.read(@path))
|
86
|
+
File.open(@cache_path, "w") { |f| f.write(@template.compiled) }
|
87
|
+
else
|
88
|
+
@template = template_class.new(File.read(@cache_path), true)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def result(binding)
|
93
|
+
@template.result(binding, @cache_path)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# Copyright (c) 2010 Samuel Williams. Released under the GNU GPLv3.
|
2
|
+
#
|
3
|
+
# This program is free software: you can redistribute it and/or modify
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
6
|
+
# (at your option) any later version.
|
7
|
+
#
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
+
# GNU General Public License for more details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU General Public License
|
14
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
require 'active_support'
|
17
|
+
|
18
|
+
class Date
|
19
|
+
alias_method :old_cmp, :<=>
|
20
|
+
|
21
|
+
def <=> (other)
|
22
|
+
# Comparing a Date with something that has a time component truncates the time
|
23
|
+
# component, thus we need to check if the other object has a more exact comparison
|
24
|
+
# function.
|
25
|
+
if other.respond_to?(:hour)
|
26
|
+
return (other <=> self) * -1
|
27
|
+
else
|
28
|
+
old_cmp(other)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class String
|
34
|
+
HTML_ESCAPE = {"&" => "&", "<" => "<", ">" => ">", "\"" => """}
|
35
|
+
HTML_ESCAPE_PATTERN = Regexp.new("[" + Regexp.quote(HTML_ESCAPE.keys.join) + "]")
|
36
|
+
|
37
|
+
def to_html
|
38
|
+
gsub(HTML_ESCAPE_PATTERN){|c| HTML_ESCAPE[c]}
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_title
|
42
|
+
(" " + self).gsub(/[ \-_](.)/){" " + $1.upcase}
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_snake
|
46
|
+
self.gsub("::", "").gsub(/([A-Z]+)/){"_" + $1.downcase}.sub(/^_+/, "")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class Hash
|
51
|
+
def symbolize_keys
|
52
|
+
inject({}) do |options, (key, value)|
|
53
|
+
options[(key.to_sym rescue key) || key] = value
|
54
|
+
options
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class Array
|
60
|
+
def find_index(&block)
|
61
|
+
each_with_index do |item, index|
|
62
|
+
if yield(item)
|
63
|
+
return index
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
return nil
|
68
|
+
end
|
69
|
+
|
70
|
+
def split_at(&block)
|
71
|
+
index = find_index(&block)
|
72
|
+
|
73
|
+
if index
|
74
|
+
return [self[0...index], self[index], self[index+1..-1]]
|
75
|
+
end
|
76
|
+
|
77
|
+
return [[], nil, []]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
if defined? Rack
|
82
|
+
class Rack::Request
|
83
|
+
def self.new(*args)
|
84
|
+
super(*args)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|