utopia 0.9.17
Sign up to get free protection for your applications and to get access to all the features.
- 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
|