homura-runtime 0.3.3 → 0.3.4
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/lib/homura/runtime/version.rb +1 -1
- data/vendor/rack/auth/abstract/handler.rb +41 -0
- data/vendor/rack/auth/abstract/request.rb +51 -0
- data/vendor/rack/auth/basic.rb +58 -0
- data/vendor/rack/bad_request.rb +8 -0
- data/vendor/rack/body_proxy.rb +63 -0
- data/vendor/rack/builder.rb +315 -0
- data/vendor/rack/cascade.rb +67 -0
- data/vendor/rack/common_logger.rb +94 -0
- data/vendor/rack/conditional_get.rb +87 -0
- data/vendor/rack/config.rb +22 -0
- data/vendor/rack/constants.rb +68 -0
- data/vendor/rack/content_length.rb +34 -0
- data/vendor/rack/content_type.rb +33 -0
- data/vendor/rack/deflater.rb +159 -0
- data/vendor/rack/directory.rb +210 -0
- data/vendor/rack/etag.rb +71 -0
- data/vendor/rack/events.rb +172 -0
- data/vendor/rack/files.rb +224 -0
- data/vendor/rack/head.rb +25 -0
- data/vendor/rack/headers.rb +238 -0
- data/vendor/rack/lint.rb +1000 -0
- data/vendor/rack/lock.rb +29 -0
- data/vendor/rack/media_type.rb +42 -0
- data/vendor/rack/method_override.rb +56 -0
- data/vendor/rack/mime.rb +694 -0
- data/vendor/rack/mock.rb +3 -0
- data/vendor/rack/mock_request.rb +161 -0
- data/vendor/rack/mock_response.rb +147 -0
- data/vendor/rack/multipart/generator.rb +99 -0
- data/vendor/rack/multipart/parser.rb +586 -0
- data/vendor/rack/multipart/uploaded_file.rb +82 -0
- data/vendor/rack/multipart.rb +77 -0
- data/vendor/rack/null_logger.rb +48 -0
- data/vendor/rack/protection/authenticity_token.rb +256 -0
- data/vendor/rack/protection/base.rb +140 -0
- data/vendor/rack/protection/content_security_policy.rb +80 -0
- data/vendor/rack/protection/cookie_tossing.rb +77 -0
- data/vendor/rack/protection/escaped_params.rb +93 -0
- data/vendor/rack/protection/form_token.rb +25 -0
- data/vendor/rack/protection/frame_options.rb +39 -0
- data/vendor/rack/protection/http_origin.rb +43 -0
- data/vendor/rack/protection/ip_spoofing.rb +27 -0
- data/vendor/rack/protection/json_csrf.rb +60 -0
- data/vendor/rack/protection/path_traversal.rb +45 -0
- data/vendor/rack/protection/referrer_policy.rb +27 -0
- data/vendor/rack/protection/remote_referrer.rb +22 -0
- data/vendor/rack/protection/remote_token.rb +24 -0
- data/vendor/rack/protection/session_hijacking.rb +37 -0
- data/vendor/rack/protection/strict_transport.rb +41 -0
- data/vendor/rack/protection/version.rb +7 -0
- data/vendor/rack/protection/xss_header.rb +27 -0
- data/vendor/rack/protection.rb +58 -0
- data/vendor/rack/query_parser.rb +261 -0
- data/vendor/rack/recursive.rb +66 -0
- data/vendor/rack/reloader.rb +112 -0
- data/vendor/rack/request.rb +818 -0
- data/vendor/rack/response.rb +403 -0
- data/vendor/rack/rewindable_input.rb +116 -0
- data/vendor/rack/runtime.rb +35 -0
- data/vendor/rack/sendfile.rb +197 -0
- data/vendor/rack/session/abstract/id.rb +533 -0
- data/vendor/rack/session/constants.rb +13 -0
- data/vendor/rack/session/cookie.rb +292 -0
- data/vendor/rack/session/encryptor.rb +415 -0
- data/vendor/rack/session/pool.rb +76 -0
- data/vendor/rack/session/version.rb +10 -0
- data/vendor/rack/session.rb +12 -0
- data/vendor/rack/show_exceptions.rb +433 -0
- data/vendor/rack/show_status.rb +121 -0
- data/vendor/rack/static.rb +188 -0
- data/vendor/rack/tempfile_reaper.rb +44 -0
- data/vendor/rack/urlmap.rb +99 -0
- data/vendor/rack/utils.rb +631 -0
- data/vendor/rack/version.rb +17 -0
- data/vendor/rack.rb +66 -0
- metadata +76 -1
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'bad_request'
|
|
4
|
+
require 'uri'
|
|
5
|
+
|
|
6
|
+
module Rack
|
|
7
|
+
class QueryParser
|
|
8
|
+
DEFAULT_SEP = /& */n
|
|
9
|
+
COMMON_SEP = { ";" => /; */n, ";," => /[;,] */n, "&" => /& */n }
|
|
10
|
+
|
|
11
|
+
# ParameterTypeError is the error that is raised when incoming structural
|
|
12
|
+
# parameters (parsed by parse_nested_query) contain conflicting types.
|
|
13
|
+
class ParameterTypeError < TypeError
|
|
14
|
+
include BadRequest
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# InvalidParameterError is the error that is raised when incoming structural
|
|
18
|
+
# parameters (parsed by parse_nested_query) contain invalid format or byte
|
|
19
|
+
# sequence.
|
|
20
|
+
class InvalidParameterError < ArgumentError
|
|
21
|
+
include BadRequest
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# QueryLimitError is for errors raised when the query provided exceeds one
|
|
25
|
+
# of the query parser limits.
|
|
26
|
+
class QueryLimitError < RangeError
|
|
27
|
+
include BadRequest
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# ParamsTooDeepError is the old name for the error that is raised when params
|
|
31
|
+
# are recursively nested over the specified limit. Make it the same as
|
|
32
|
+
# as QueryLimitError, so that code that rescues ParamsTooDeepError error
|
|
33
|
+
# to handle bad query strings also now handles other limits.
|
|
34
|
+
ParamsTooDeepError = QueryLimitError
|
|
35
|
+
|
|
36
|
+
def self.make_default(param_depth_limit, **options)
|
|
37
|
+
new(Params, param_depth_limit, **options)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
attr_reader :param_depth_limit
|
|
41
|
+
|
|
42
|
+
env_int = lambda do |key, val|
|
|
43
|
+
if str_val = ENV[key]
|
|
44
|
+
begin
|
|
45
|
+
val = Integer(str_val, 10)
|
|
46
|
+
rescue ArgumentError
|
|
47
|
+
raise ArgumentError, "non-integer value provided for environment variable #{key}"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
val
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
BYTESIZE_LIMIT = env_int.call("RACK_QUERY_PARSER_BYTESIZE_LIMIT", 4194304)
|
|
55
|
+
private_constant :BYTESIZE_LIMIT
|
|
56
|
+
|
|
57
|
+
PARAMS_LIMIT = env_int.call("RACK_QUERY_PARSER_PARAMS_LIMIT", 4096)
|
|
58
|
+
private_constant :PARAMS_LIMIT
|
|
59
|
+
|
|
60
|
+
attr_reader :bytesize_limit
|
|
61
|
+
|
|
62
|
+
def initialize(params_class, param_depth_limit, bytesize_limit: BYTESIZE_LIMIT, params_limit: PARAMS_LIMIT)
|
|
63
|
+
@params_class = params_class
|
|
64
|
+
@param_depth_limit = param_depth_limit
|
|
65
|
+
@bytesize_limit = bytesize_limit
|
|
66
|
+
@params_limit = params_limit
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Stolen from Mongrel, with some small modifications:
|
|
70
|
+
# Parses a query string by breaking it up at the '&'. You can also use this
|
|
71
|
+
# to parse cookies by changing the characters used in the second parameter
|
|
72
|
+
# (which defaults to '&').
|
|
73
|
+
def parse_query(qs, separator = nil, &unescaper)
|
|
74
|
+
params = make_params
|
|
75
|
+
|
|
76
|
+
each_query_pair(qs, separator, unescaper) do |k, v|
|
|
77
|
+
if cur = params[k]
|
|
78
|
+
if cur.class == Array
|
|
79
|
+
params[k] << v
|
|
80
|
+
else
|
|
81
|
+
params[k] = [cur, v]
|
|
82
|
+
end
|
|
83
|
+
else
|
|
84
|
+
params[k] = v
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
return params.to_h
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Parses a query string by breaking it up at the '&', returning all key-value
|
|
92
|
+
# pairs as an array of [key, value] arrays. Unlike parse_query, this preserves
|
|
93
|
+
# all duplicate keys rather than collapsing them.
|
|
94
|
+
def parse_query_pairs(qs, separator = nil)
|
|
95
|
+
pairs = []
|
|
96
|
+
|
|
97
|
+
each_query_pair(qs, separator) do |k, v|
|
|
98
|
+
pairs << [k, v]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
pairs
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# parse_nested_query expands a query string into structural types. Supported
|
|
105
|
+
# types are Arrays, Hashes and basic value types. It is possible to supply
|
|
106
|
+
# query strings with parameters of conflicting types, in this case a
|
|
107
|
+
# ParameterTypeError is raised. Users are encouraged to return a 400 in this
|
|
108
|
+
# case.
|
|
109
|
+
def parse_nested_query(qs, separator = nil)
|
|
110
|
+
params = make_params
|
|
111
|
+
|
|
112
|
+
each_query_pair(qs, separator) do |k, v|
|
|
113
|
+
_normalize_params(params, k, v, 0)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
return params.to_h
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# normalize_params recursively expands parameters into structural types. If
|
|
120
|
+
# the structural types represented by two different parameter names are in
|
|
121
|
+
# conflict, a ParameterTypeError is raised. The depth argument is deprecated
|
|
122
|
+
# and should no longer be used, it is kept for backwards compatibility with
|
|
123
|
+
# earlier versions of rack.
|
|
124
|
+
def normalize_params(params, name, v, _depth=nil)
|
|
125
|
+
_normalize_params(params, name, v, 0)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
private def _normalize_params(params, name, v, depth)
|
|
129
|
+
raise ParamsTooDeepError if depth >= param_depth_limit
|
|
130
|
+
|
|
131
|
+
if !name
|
|
132
|
+
# nil name, treat same as empty string (required by tests)
|
|
133
|
+
k = after = ''
|
|
134
|
+
elsif depth == 0
|
|
135
|
+
# Start of parsing, don't treat [] or [ at start of string specially
|
|
136
|
+
if start = name.index('[', 1)
|
|
137
|
+
# Start of parameter nesting, use part before brackets as key
|
|
138
|
+
k = name[0, start]
|
|
139
|
+
after = name[start, name.length]
|
|
140
|
+
else
|
|
141
|
+
# Plain parameter with no nesting
|
|
142
|
+
k = name
|
|
143
|
+
after = ''
|
|
144
|
+
end
|
|
145
|
+
elsif name.start_with?('[]')
|
|
146
|
+
# Array nesting
|
|
147
|
+
k = '[]'
|
|
148
|
+
after = name[2, name.length]
|
|
149
|
+
elsif name.start_with?('[') && (start = name.index(']', 1))
|
|
150
|
+
# Hash nesting, use the part inside brackets as the key
|
|
151
|
+
k = name[1, start-1]
|
|
152
|
+
after = name[start+1, name.length]
|
|
153
|
+
else
|
|
154
|
+
# Probably malformed input, nested but not starting with [
|
|
155
|
+
# treat full name as key for backwards compatibility.
|
|
156
|
+
k = name
|
|
157
|
+
after = ''
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
return if k.empty?
|
|
161
|
+
|
|
162
|
+
if after == ''
|
|
163
|
+
if k == '[]' && depth != 0
|
|
164
|
+
return [v]
|
|
165
|
+
else
|
|
166
|
+
params[k] = v
|
|
167
|
+
end
|
|
168
|
+
elsif after == "["
|
|
169
|
+
params[name] = v
|
|
170
|
+
elsif after == "[]"
|
|
171
|
+
params[k] ||= []
|
|
172
|
+
raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
|
173
|
+
params[k] << v
|
|
174
|
+
elsif after.start_with?('[]')
|
|
175
|
+
# Recognize x[][y] (hash inside array) parameters
|
|
176
|
+
unless after[2] == '[' && after.end_with?(']') && (child_key = after[3, after.length-4]) && !child_key.empty? && !child_key.index('[') && !child_key.index(']')
|
|
177
|
+
# Handle other nested array parameters
|
|
178
|
+
child_key = after[2, after.length]
|
|
179
|
+
end
|
|
180
|
+
params[k] ||= []
|
|
181
|
+
raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
|
182
|
+
if params_hash_type?(params[k].last) && !params_hash_has_key?(params[k].last, child_key)
|
|
183
|
+
_normalize_params(params[k].last, child_key, v, depth + 1)
|
|
184
|
+
else
|
|
185
|
+
params[k] << _normalize_params(make_params, child_key, v, depth + 1)
|
|
186
|
+
end
|
|
187
|
+
else
|
|
188
|
+
params[k] ||= make_params
|
|
189
|
+
raise ParameterTypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
|
|
190
|
+
params[k] = _normalize_params(params[k], after, v, depth + 1)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
params
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def make_params
|
|
197
|
+
@params_class.new
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def new_depth_limit(param_depth_limit)
|
|
201
|
+
self.class.new @params_class, param_depth_limit
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
private
|
|
205
|
+
|
|
206
|
+
def params_hash_type?(obj)
|
|
207
|
+
obj.kind_of?(@params_class)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def params_hash_has_key?(hash, key)
|
|
211
|
+
return false if /\[\]/.match?(key)
|
|
212
|
+
|
|
213
|
+
key.split(/[\[\]]+/).inject(hash) do |h, part|
|
|
214
|
+
next h if part == ''
|
|
215
|
+
return false unless params_hash_type?(h) && h.key?(part)
|
|
216
|
+
h[part]
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
true
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def each_query_pair(qs, separator, unescaper = nil)
|
|
223
|
+
return if !qs || qs.empty?
|
|
224
|
+
|
|
225
|
+
if qs.bytesize > @bytesize_limit
|
|
226
|
+
raise QueryLimitError, "total query size exceeds limit (#{@bytesize_limit})"
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
pairs = qs.split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP, @params_limit + 1)
|
|
230
|
+
|
|
231
|
+
if pairs.size > @params_limit
|
|
232
|
+
param_count = pairs.size + pairs.last.count(separator || "&")
|
|
233
|
+
raise QueryLimitError, "total number of query parameters (#{param_count}) exceeds limit (#{@params_limit})"
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
if unescaper
|
|
237
|
+
pairs.each do |p|
|
|
238
|
+
next if p.empty?
|
|
239
|
+
k, v = p.split('=', 2).map!(&unescaper)
|
|
240
|
+
yield k, v
|
|
241
|
+
end
|
|
242
|
+
else
|
|
243
|
+
pairs.each do |p|
|
|
244
|
+
next if p.empty?
|
|
245
|
+
k, v = p.split('=', 2).map! { |s| unescape(s) }
|
|
246
|
+
yield k, v
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
rescue ArgumentError => e
|
|
250
|
+
raise InvalidParameterError, e.message, e.backtrace
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def unescape(string, encoding = Encoding::UTF_8)
|
|
254
|
+
URI.decode_www_form_component(string, encoding)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
class Params < Hash
|
|
258
|
+
alias_method :to_params_hash, :to_h
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'uri'
|
|
4
|
+
|
|
5
|
+
require_relative 'constants'
|
|
6
|
+
|
|
7
|
+
module Rack
|
|
8
|
+
# Rack::ForwardRequest gets caught by Rack::Recursive and redirects
|
|
9
|
+
# the current request to the app at +url+.
|
|
10
|
+
#
|
|
11
|
+
# raise ForwardRequest.new("/not-found")
|
|
12
|
+
#
|
|
13
|
+
|
|
14
|
+
class ForwardRequest < Exception
|
|
15
|
+
attr_reader :url, :env
|
|
16
|
+
|
|
17
|
+
def initialize(url, env = {})
|
|
18
|
+
@url = URI(url)
|
|
19
|
+
@env = env
|
|
20
|
+
|
|
21
|
+
@env[PATH_INFO] = @url.path
|
|
22
|
+
@env[QUERY_STRING] = @url.query if @url.query
|
|
23
|
+
@env[HTTP_HOST] = @url.host if @url.host
|
|
24
|
+
@env[HTTP_PORT] = @url.port if @url.port
|
|
25
|
+
@env[RACK_URL_SCHEME] = @url.scheme if @url.scheme
|
|
26
|
+
|
|
27
|
+
super "forwarding to #{url}"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Rack::Recursive allows applications called down the chain to
|
|
32
|
+
# include data from other applications (by using
|
|
33
|
+
# <tt>rack['rack.recursive.include'][...]</tt> or raise a
|
|
34
|
+
# ForwardRequest to redirect internally.
|
|
35
|
+
|
|
36
|
+
class Recursive
|
|
37
|
+
def initialize(app)
|
|
38
|
+
@app = app
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def call(env)
|
|
42
|
+
dup._call(env)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def _call(env)
|
|
46
|
+
@script_name = env[SCRIPT_NAME]
|
|
47
|
+
@app.call(env.merge(RACK_RECURSIVE_INCLUDE => method(:include)))
|
|
48
|
+
rescue ForwardRequest => req
|
|
49
|
+
call(env.merge(req.env))
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def include(env, path)
|
|
53
|
+
unless path.index(@script_name) == 0 && (path[@script_name.size] == ?/ ||
|
|
54
|
+
path[@script_name.size].nil?)
|
|
55
|
+
raise ArgumentError, "can only include below #{@script_name}, not #{path}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
env = env.merge(PATH_INFO => path,
|
|
59
|
+
SCRIPT_NAME => @script_name,
|
|
60
|
+
REQUEST_METHOD => GET,
|
|
61
|
+
"CONTENT_LENGTH" => "0", "CONTENT_TYPE" => "",
|
|
62
|
+
RACK_INPUT => StringIO.new(""))
|
|
63
|
+
@app.call(env)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright (C) 2009-2018 Michael Fellinger <m.fellinger@gmail.com>
|
|
4
|
+
# Rack::Reloader is subject to the terms of an MIT-style license.
|
|
5
|
+
# See MIT-LICENSE or https://opensource.org/licenses/MIT.
|
|
6
|
+
|
|
7
|
+
require 'pathname'
|
|
8
|
+
|
|
9
|
+
module Rack
|
|
10
|
+
|
|
11
|
+
# High performant source reloader
|
|
12
|
+
#
|
|
13
|
+
# This class acts as Rack middleware.
|
|
14
|
+
#
|
|
15
|
+
# What makes it especially suited for use in a production environment is that
|
|
16
|
+
# any file will only be checked once and there will only be made one system
|
|
17
|
+
# call stat(2).
|
|
18
|
+
#
|
|
19
|
+
# Please note that this will not reload files in the background, it does so
|
|
20
|
+
# only when actively called.
|
|
21
|
+
#
|
|
22
|
+
# It is performing a check/reload cycle at the start of every request, but
|
|
23
|
+
# also respects a cool down time, during which nothing will be done.
|
|
24
|
+
class Reloader
|
|
25
|
+
def initialize(app, cooldown = 10, backend = Stat)
|
|
26
|
+
@app = app
|
|
27
|
+
@cooldown = cooldown
|
|
28
|
+
@last = (Time.now - cooldown)
|
|
29
|
+
@cache = {}
|
|
30
|
+
@mtimes = {}
|
|
31
|
+
@reload_mutex = Mutex.new
|
|
32
|
+
|
|
33
|
+
extend backend
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def call(env)
|
|
37
|
+
if @cooldown and Time.now > @last + @cooldown
|
|
38
|
+
if Thread.list.size > 1
|
|
39
|
+
@reload_mutex.synchronize{ reload! }
|
|
40
|
+
else
|
|
41
|
+
reload!
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
@last = Time.now
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
@app.call(env)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def reload!(stderr = $stderr)
|
|
51
|
+
rotation do |file, mtime|
|
|
52
|
+
previous_mtime = @mtimes[file] ||= mtime
|
|
53
|
+
safe_load(file, mtime, stderr) if mtime > previous_mtime
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# A safe Kernel::load, issuing the hooks depending on the results
|
|
58
|
+
def safe_load(file, mtime, stderr = $stderr)
|
|
59
|
+
load(file)
|
|
60
|
+
stderr.puts "#{self.class}: reloaded `#{file}'"
|
|
61
|
+
file
|
|
62
|
+
rescue LoadError, SyntaxError => ex
|
|
63
|
+
stderr.puts ex
|
|
64
|
+
ensure
|
|
65
|
+
@mtimes[file] = mtime
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
module Stat
|
|
69
|
+
def rotation
|
|
70
|
+
files = [$0, *$LOADED_FEATURES].uniq
|
|
71
|
+
paths = ['./', *$LOAD_PATH].uniq
|
|
72
|
+
|
|
73
|
+
files.map{|file|
|
|
74
|
+
next if /\.(so|bundle)$/.match?(file) # cannot reload compiled files
|
|
75
|
+
|
|
76
|
+
found, stat = figure_path(file, paths)
|
|
77
|
+
next unless found && stat && mtime = stat.mtime
|
|
78
|
+
|
|
79
|
+
@cache[file] = found
|
|
80
|
+
|
|
81
|
+
yield(found, mtime)
|
|
82
|
+
}.compact
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Takes a relative or absolute +file+ name, a couple possible +paths+ that
|
|
86
|
+
# the +file+ might reside in. Returns the full path and File::Stat for the
|
|
87
|
+
# path.
|
|
88
|
+
def figure_path(file, paths)
|
|
89
|
+
found = @cache[file]
|
|
90
|
+
found = file if !found and Pathname.new(file).absolute?
|
|
91
|
+
found, stat = safe_stat(found)
|
|
92
|
+
return found, stat if found
|
|
93
|
+
|
|
94
|
+
paths.find do |possible_path|
|
|
95
|
+
path = ::File.join(possible_path, file)
|
|
96
|
+
found, stat = safe_stat(path)
|
|
97
|
+
return ::File.expand_path(found), stat if found
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
return false, false
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def safe_stat(file)
|
|
104
|
+
return unless file
|
|
105
|
+
stat = ::File.stat(file)
|
|
106
|
+
return file, stat if stat.file?
|
|
107
|
+
rescue Errno::ENOENT, Errno::ENOTDIR, Errno::ESRCH
|
|
108
|
+
@cache.delete(file) and false
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|