blobsterix 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +27 -0
- data/CHANGELOG.txt +13 -0
- data/Gemfile +16 -0
- data/LICENSE +22 -0
- data/README.md +122 -0
- data/Rakefile +13 -0
- data/bin/blobsterix +152 -0
- data/bin/test +26 -0
- data/blobsterix.gemspec +39 -0
- data/config/lighttpd.conf +50 -0
- data/lib/blobsterix.rb +213 -0
- data/lib/blobsterix/blob/blob_api.rb +55 -0
- data/lib/blobsterix/blob/blob_url_helper.rb +55 -0
- data/lib/blobsterix/helper/accept_type.rb +62 -0
- data/lib/blobsterix/helper/blob_access.rb +73 -0
- data/lib/blobsterix/helper/config_loader.rb +33 -0
- data/lib/blobsterix/helper/data_response.rb +54 -0
- data/lib/blobsterix/helper/http.rb +47 -0
- data/lib/blobsterix/helper/logable.rb +11 -0
- data/lib/blobsterix/helper/murmur.rb +137 -0
- data/lib/blobsterix/helper/status_info.rb +42 -0
- data/lib/blobsterix/helper/template_renderer.rb +39 -0
- data/lib/blobsterix/mimemagic/magic.rb +138 -0
- data/lib/blobsterix/mimemagic/tables.rb +1770 -0
- data/lib/blobsterix/mimemagic/version.rb +5 -0
- data/lib/blobsterix/router/app_router.rb +134 -0
- data/lib/blobsterix/s3/s3_api.rb +92 -0
- data/lib/blobsterix/s3/s3_url_helper.rb +93 -0
- data/lib/blobsterix/service.rb +34 -0
- data/lib/blobsterix/status/status_api.rb +62 -0
- data/lib/blobsterix/status/status_url_helper.rb +11 -0
- data/lib/blobsterix/storage/blob_meta_data.rb +60 -0
- data/lib/blobsterix/storage/bucket.rb +36 -0
- data/lib/blobsterix/storage/bucket_entry.rb +29 -0
- data/lib/blobsterix/storage/bucket_list.rb +26 -0
- data/lib/blobsterix/storage/cache.rb +90 -0
- data/lib/blobsterix/storage/file_system.rb +132 -0
- data/lib/blobsterix/storage/file_system_meta_data.rb +136 -0
- data/lib/blobsterix/storage/storage.rb +30 -0
- data/lib/blobsterix/transformation/image_transformation.rb +439 -0
- data/lib/blobsterix/transformation/transformation.rb +30 -0
- data/lib/blobsterix/transformation/transformation_chain.rb +78 -0
- data/lib/blobsterix/transformation/transformation_manager.rb +115 -0
- data/lib/blobsterix/version.rb +3 -0
- data/scripts/download.rb +30 -0
- data/scripts/test +6 -0
- data/spec/lib/blob/blob_api_spec.rb +81 -0
- data/spec/lib/helper/blob_access_spec.rb +72 -0
- data/spec/lib/s3/s3_api_spec.rb +183 -0
- data/spec/lib/service_spec.rb +12 -0
- data/spec/lib/status/status_api_spec.rb +42 -0
- data/spec/lib/storage/cache_spec.rb +135 -0
- data/spec/lib/storage/file_system_spec.rb +84 -0
- data/spec/spec_helper.rb +139 -0
- data/templates/app/Gemfile +12 -0
- data/templates/app/Rakefile +7 -0
- data/templates/app/config.rb +61 -0
- data/templates/app/config/environments/development.rb +40 -0
- data/templates/app/config/environments/production.rb +40 -0
- data/templates/app/storages/.keep +0 -0
- data/templates/app/transformators/.keep +0 -0
- data/templates/app/views/.keep +0 -0
- data/templates/storage_template.rb +30 -0
- data/templates/transformation_template.rb +41 -0
- data/templates/views/error_page.erb +18 -0
- data/templates/views/status_page.erb +31 -0
- metadata +325 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
module Blobsterix
|
2
|
+
module ConfigLoader
|
3
|
+
def load_blobsterix_config
|
4
|
+
require_storages
|
5
|
+
require_transformators
|
6
|
+
require_config
|
7
|
+
end
|
8
|
+
|
9
|
+
def require_config()
|
10
|
+
require Blobsterix.root.join("config.rb") if (File.exist?(Blobsterix.root.join("config.rb")))
|
11
|
+
end
|
12
|
+
|
13
|
+
def require_transformators()
|
14
|
+
trafo_dir = Blobsterix.root.join("transformators")
|
15
|
+
return if not File.exist?(trafo_dir)
|
16
|
+
Dir.entries(trafo_dir).each{|dir|
|
17
|
+
if !File.directory? File.join(trafo_dir,dir) and !(dir =='.' || dir == '..')
|
18
|
+
require "#{File.join(trafo_dir,dir)}"
|
19
|
+
end
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def require_storages()
|
24
|
+
storages_dir = Blobsterix.root.join("storages")
|
25
|
+
return if not File.exist?(storages_dir)
|
26
|
+
Dir.entries(storages_dir).each{|dir|
|
27
|
+
if !File.directory? File.join(storages_dir,dir) and !(dir =='.' || dir == '..')
|
28
|
+
require "#{File.join(storages_dir,dir)}"
|
29
|
+
end
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Blobsterix
|
2
|
+
module Http
|
3
|
+
class DataResponse
|
4
|
+
attr_reader :meta, :with_data, :etag, :env
|
5
|
+
|
6
|
+
def initialize(_meta, _with_data=true, _etag=nil, _env = nil)
|
7
|
+
@meta = _meta
|
8
|
+
@with_data = _with_data
|
9
|
+
@etag = _etag
|
10
|
+
@env = _env
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(xfile=false, allow_chunks=true)
|
14
|
+
if not meta.valid
|
15
|
+
Http.NotFound()
|
16
|
+
elsif xfile and etag != meta.etag
|
17
|
+
[200, meta.header.merge({"X-Sendfile" => meta.path}), ""]
|
18
|
+
elsif etag != meta.etag
|
19
|
+
if env != nil && meta.size > 30000 && allow_chunks
|
20
|
+
chunkresponse
|
21
|
+
else
|
22
|
+
[200, meta.header, (with_data ? meta.data : "")]
|
23
|
+
end
|
24
|
+
else
|
25
|
+
[304, meta.header, ""]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def chunkresponse
|
31
|
+
f = File.open(meta.path)
|
32
|
+
EM.next_tick do
|
33
|
+
send_chunk(f)
|
34
|
+
end
|
35
|
+
[200, meta.header.merge(Goliath::Response::CHUNKED_STREAM_HEADERS), (with_data ? Goliath::Response::STREAMING : "")]
|
36
|
+
end
|
37
|
+
|
38
|
+
def send_chunk(file)
|
39
|
+
dat = file.read(10000)
|
40
|
+
again = if dat != nil
|
41
|
+
env.chunked_stream_send(dat)
|
42
|
+
true
|
43
|
+
else
|
44
|
+
file.close
|
45
|
+
env.chunked_stream_close
|
46
|
+
false
|
47
|
+
end
|
48
|
+
EM.next_tick do
|
49
|
+
send_chunk(file)
|
50
|
+
end if again
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Blobsterix
|
2
|
+
module Http
|
3
|
+
def self.renderer
|
4
|
+
@@renderer||=(Blobsterix.respond_to?(:env) && Blobsterix.env == :production) ? TemplateRenderer.new(binding) : ReloadTemplateRenderer.new(binding)
|
5
|
+
end
|
6
|
+
def self.error_object_binding(obj)
|
7
|
+
obj||={}
|
8
|
+
def obj.get_binding
|
9
|
+
binding
|
10
|
+
end
|
11
|
+
def obj.title
|
12
|
+
self[:title]
|
13
|
+
end
|
14
|
+
def obj.content
|
15
|
+
self[:content]
|
16
|
+
end
|
17
|
+
def obj.error_code
|
18
|
+
self[:error_code]
|
19
|
+
end
|
20
|
+
obj.get_binding
|
21
|
+
end
|
22
|
+
def self.NextApi(data="Not Found", content_type="txt")
|
23
|
+
[600, {"Content-Type" => MimeMagic.by_extension(content_type).type}, data]
|
24
|
+
end
|
25
|
+
def self.NotFound(data="Not Found", content_type="html")
|
26
|
+
[404, {"Content-Type" => MimeMagic.by_extension(content_type).type}, renderer.render("error_page", error_object_binding(:title=>"Not Found", :content=>data, :error_code => 404))]
|
27
|
+
end
|
28
|
+
def self.ServerError(data="Server Error", content_type="html")
|
29
|
+
[500, {"Content-Type" => MimeMagic.by_extension(content_type).type}, renderer.render("error_page", error_object_binding(:title=>"Server Error", :content=>data, :error_code => 500))]
|
30
|
+
end
|
31
|
+
def self.NotAllowed(data="Not Allowed", content_type="html")
|
32
|
+
[403, {"Content-Type" => MimeMagic.by_extension(content_type).type}, renderer.render("error_page", error_object_binding(:title=>"Not Allowed", :content=>data, :error_code => 403))]
|
33
|
+
end
|
34
|
+
def self.NotAuthorized(data="Not Authorized", content_type="html")
|
35
|
+
[401, {"Content-Type" => MimeMagic.by_extension(content_type).type}, renderer.render("error_page", error_object_binding(:title=>"Not Authorized", :content=>data, :error_code => 401))]
|
36
|
+
end
|
37
|
+
def self.OK(data="", content_type="txt")
|
38
|
+
[200, {"Content-Type" => MimeMagic.by_extension(content_type).type}, data]
|
39
|
+
end
|
40
|
+
def self.Response(status_code=200, data="", content_type="txt")
|
41
|
+
[status_code, {"Content-Type" => MimeMagic.by_extension(content_type).type}, data]
|
42
|
+
end
|
43
|
+
def self.OK_no_data(data="", content_type="txt")
|
44
|
+
[204, {}, ""]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
#only usefull to hash strings
|
2
|
+
class Murmur
|
3
|
+
def self.force_overflow_signed(i)
|
4
|
+
force_overflow_unsigned(i + 2**31) - 2**31
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.force_overflow_unsigned_16(i)
|
8
|
+
i % 2**16 # or equivalently: i & 0xffffffff
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.force_overflow_unsigned(i)
|
12
|
+
i % 2**32 # or equivalently: i & 0xffffffff
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.force_overflow_unsigned_64(i)
|
16
|
+
i % 2**64 # or equivalently: i & 0xffffffff
|
17
|
+
end
|
18
|
+
|
19
|
+
#64bit processors
|
20
|
+
def self.Hash64A (key)
|
21
|
+
len = key.size
|
22
|
+
seed = 11
|
23
|
+
|
24
|
+
m = 0xc6a4a7935bd1e995
|
25
|
+
r = 47
|
26
|
+
|
27
|
+
h = seed ^ len;
|
28
|
+
|
29
|
+
data = String.new(key)
|
30
|
+
|
31
|
+
while len >= 8
|
32
|
+
k = data.slice!(0..7).unpack("Q")[0]
|
33
|
+
|
34
|
+
k = force_overflow_unsigned_64(k * m)
|
35
|
+
k ^= k >> r
|
36
|
+
k = force_overflow_unsigned_64(k * m)
|
37
|
+
|
38
|
+
h ^= k;
|
39
|
+
h = force_overflow_unsigned_64(h * m)
|
40
|
+
|
41
|
+
len-=8
|
42
|
+
end
|
43
|
+
|
44
|
+
h ^= data.slice(6).to_i << 48 if len == 7
|
45
|
+
h ^= data.slice(5).to_i << 40 if len >= 6
|
46
|
+
h ^= data.slice(4).to_i << 32 if len >= 5
|
47
|
+
h ^= data.slice(3).to_i << 24 if len >= 4
|
48
|
+
h ^= data.slice(2).to_i << 16 if len >= 3
|
49
|
+
h ^= data.slice(1).to_i << 8 if len >= 2
|
50
|
+
h ^= data.slice(0).to_i if len >= 1
|
51
|
+
|
52
|
+
h = force_overflow_unsigned_64(h * m) if len
|
53
|
+
|
54
|
+
h ^= h >> r
|
55
|
+
h = force_overflow_unsigned_64(h * m)
|
56
|
+
h ^= h >> r
|
57
|
+
|
58
|
+
h
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.get_num(num)
|
62
|
+
return num if num.class == Fixnum
|
63
|
+
num.to_s.unpack("C")[0]
|
64
|
+
end
|
65
|
+
|
66
|
+
#32bit processors
|
67
|
+
def self.Hash64B(key)
|
68
|
+
len = key.size
|
69
|
+
seed = 11
|
70
|
+
|
71
|
+
m = 0x5bd1e995 #1540483477
|
72
|
+
r = 24
|
73
|
+
|
74
|
+
h1 = seed ^ len
|
75
|
+
h2 = 0
|
76
|
+
|
77
|
+
data = String.new(key)#.force_encoding('ASCII-8BIT')
|
78
|
+
|
79
|
+
while len >= 8
|
80
|
+
k1 = data.slice!(0..3).unpack("I")[0]
|
81
|
+
|
82
|
+
k1 = force_overflow_unsigned(k1 * m)
|
83
|
+
k1 ^= k1 >> r
|
84
|
+
|
85
|
+
k1 = force_overflow_unsigned(k1 * m)
|
86
|
+
h1 = force_overflow_unsigned(h1 * m)
|
87
|
+
|
88
|
+
h1 ^= k1
|
89
|
+
len -= 4
|
90
|
+
|
91
|
+
|
92
|
+
k2 = data.slice!(0..3).unpack("I")[0]
|
93
|
+
|
94
|
+
k2 = force_overflow_unsigned(k2 * m)
|
95
|
+
k2 ^= k2 >> r
|
96
|
+
|
97
|
+
k2 = force_overflow_unsigned(k2 * m)
|
98
|
+
h2 = force_overflow_unsigned(h2 * m)
|
99
|
+
|
100
|
+
h2 ^= k2
|
101
|
+
len -= 4
|
102
|
+
end
|
103
|
+
|
104
|
+
if len >= 4
|
105
|
+
k1 = data.slice!(0..3).unpack("I")[0]
|
106
|
+
|
107
|
+
k1 = force_overflow_unsigned(k1 * m)
|
108
|
+
k1 ^= k1 >> r
|
109
|
+
|
110
|
+
k1 = force_overflow_unsigned(k1 * m)
|
111
|
+
h1 = force_overflow_unsigned(h1 * m)
|
112
|
+
|
113
|
+
h1 ^= k1
|
114
|
+
len -= 4
|
115
|
+
end
|
116
|
+
|
117
|
+
h2 ^= (get_num(data[2]) << 16) if len == 3
|
118
|
+
h2 ^= (get_num(data[1]) << 8).to_i if len >= 2
|
119
|
+
h2 ^= (get_num(data[0])) if len >= 1
|
120
|
+
|
121
|
+
h2 = force_overflow_unsigned(h2 * m) if len > 0
|
122
|
+
|
123
|
+
h1 ^= h2 >> 18
|
124
|
+
h1 = force_overflow_unsigned(h1 * m)
|
125
|
+
|
126
|
+
h2 ^= h1 >> 22
|
127
|
+
h2 = force_overflow_unsigned(h2 * m)
|
128
|
+
|
129
|
+
h1 ^= h2 >> 17
|
130
|
+
h1 = force_overflow_unsigned(h1 * m)
|
131
|
+
|
132
|
+
h = h1
|
133
|
+
h = (h << 32) | h2
|
134
|
+
|
135
|
+
h
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Blobsterix
|
2
|
+
module StatusInfo
|
3
|
+
def self.boot_up
|
4
|
+
@start_time=Time.now
|
5
|
+
end
|
6
|
+
boot_up
|
7
|
+
|
8
|
+
def self.uptime
|
9
|
+
Time.now-@start_time
|
10
|
+
end
|
11
|
+
def self.cache_hit
|
12
|
+
@cache_hit||=0
|
13
|
+
end
|
14
|
+
def self.cache_hit=(obj)
|
15
|
+
@cache_hit=obj
|
16
|
+
end
|
17
|
+
def self.cache_miss
|
18
|
+
@cache_miss||=0
|
19
|
+
end
|
20
|
+
def self.cache_miss=(obj)
|
21
|
+
@cache_miss=obj
|
22
|
+
end
|
23
|
+
def self.cache_error
|
24
|
+
@cache_error||=0
|
25
|
+
end
|
26
|
+
def self.cache_error=(obj)
|
27
|
+
@cache_error=obj
|
28
|
+
end
|
29
|
+
def self.cache_access
|
30
|
+
@cache_access||=0
|
31
|
+
end
|
32
|
+
def self.cache_access=(obj)
|
33
|
+
@cache_access=obj
|
34
|
+
end
|
35
|
+
def self.connections
|
36
|
+
@connections||=0
|
37
|
+
end
|
38
|
+
def self.connections=(obj)
|
39
|
+
@connections=obj
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Blobsterix
|
2
|
+
class ReloadTemplateRenderer
|
3
|
+
def initialize(binding_)
|
4
|
+
@binding = binding_
|
5
|
+
end
|
6
|
+
|
7
|
+
def render(template_name, bind=nil)
|
8
|
+
TemplateRenderer.new(bind||@binding).render(template_name)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class TemplateRenderer
|
13
|
+
def initialize(controller_binding_)
|
14
|
+
@controller_binding=controller_binding_
|
15
|
+
end
|
16
|
+
|
17
|
+
def render(template_name, bind=nil)
|
18
|
+
template(template_name).result(bind||controller_binding)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def controller_binding
|
24
|
+
@controller_binding
|
25
|
+
end
|
26
|
+
|
27
|
+
def template(template_name)
|
28
|
+
begin
|
29
|
+
templates[template_name]||=::ERB.new(File.read(Blobsterix.root.join("views", "#{template_name}.erb")))
|
30
|
+
rescue Errno::ENOENT => e
|
31
|
+
templates[template_name]||=::ERB.new(File.read(Blobsterix.root_gem.join("templates/views", "#{template_name}.erb")))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def templates
|
36
|
+
@templates||={}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# Mime type detection
|
2
|
+
class MimeMagic
|
3
|
+
attr_reader :type, :mediatype, :subtype
|
4
|
+
|
5
|
+
# Mime type by type string
|
6
|
+
def initialize(type)
|
7
|
+
@type = type
|
8
|
+
@mediatype, @subtype = type.split('/', 2)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Add custom mime type. Arguments:
|
12
|
+
# * <i>type</i>: Mime type
|
13
|
+
# * <i>options</i>: Options hash
|
14
|
+
#
|
15
|
+
# Option keys:
|
16
|
+
# * <i>:extensions</i>: String list or single string of file extensions
|
17
|
+
# * <i>:parents</i>: String list or single string of parent mime types
|
18
|
+
# * <i>:magic</i>: Mime magic specification
|
19
|
+
# * <i>:comment</i>: Comment string
|
20
|
+
def self.add(type, options)
|
21
|
+
extensions = [options[:extensions]].flatten.compact
|
22
|
+
TYPES[type] = [extensions,
|
23
|
+
[options[:parents]].flatten.compact,
|
24
|
+
options[:comment]]
|
25
|
+
extensions.each {|ext| EXTENSIONS[ext] = type }
|
26
|
+
MAGIC.unshift [type, options[:magic]] if options[:magic]
|
27
|
+
end
|
28
|
+
|
29
|
+
# Removes a mime type from the dictionary. You might want to do this if
|
30
|
+
# you're seeing impossible conflicts (for instance, application/x-gmc-link).
|
31
|
+
# * <i>type</i>: The mime type to remove. All associated extensions and magic are removed too.
|
32
|
+
def self.remove(type)
|
33
|
+
EXTENSIONS.delete_if {|ext, t| t == type }
|
34
|
+
MAGIC.delete_if {|t, m| t == type }
|
35
|
+
TYPES.delete(type)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns true if type is a text format
|
39
|
+
def text?; mediatype == 'text' || child_of?('text/plain'); end
|
40
|
+
|
41
|
+
# Mediatype shortcuts
|
42
|
+
def image?; mediatype == 'image'; end
|
43
|
+
def audio?; mediatype == 'audio'; end
|
44
|
+
def video?; mediatype == 'video'; end
|
45
|
+
|
46
|
+
# Returns true if type is child of parent type
|
47
|
+
def child_of?(parent)
|
48
|
+
MimeMagic.child?(type, parent)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Get string list of file extensions
|
52
|
+
def extensions
|
53
|
+
TYPES.key?(type) ? TYPES[type][0] : []
|
54
|
+
end
|
55
|
+
|
56
|
+
# Get mime comment
|
57
|
+
def comment
|
58
|
+
(TYPES.key?(type) ? TYPES[type][2] : nil).to_s
|
59
|
+
end
|
60
|
+
|
61
|
+
# Lookup mime type by file extension
|
62
|
+
def self.by_extension(ext)
|
63
|
+
ext = ext.to_s.downcase
|
64
|
+
mime = ext[0..0] == '.' ? EXTENSIONS[ext[1..-1]] : EXTENSIONS[ext]
|
65
|
+
mime && new(mime)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Lookup mime type by filename
|
69
|
+
def self.by_path(path)
|
70
|
+
by_extension(File.extname(path))
|
71
|
+
end
|
72
|
+
|
73
|
+
# Lookup mime type by magic content analysis.
|
74
|
+
# This is a slow operation.
|
75
|
+
def self.by_magic(io)
|
76
|
+
mime =
|
77
|
+
unless io.respond_to?(:seek) && io.respond_to?(:read)
|
78
|
+
str = io.respond_to?(:read) ? io.read : io.to_s
|
79
|
+
str = str.force_encoding(Encoding::BINARY) if str.respond_to? :force_encoding
|
80
|
+
MAGIC.find {|type, matches| magic_match_str(str, matches) }
|
81
|
+
else
|
82
|
+
io.binmode
|
83
|
+
io.set_encoding(Encoding::BINARY) if io.respond_to?(:set_encoding)
|
84
|
+
MAGIC.find {|type, matches| magic_match_io(io, matches) }
|
85
|
+
end
|
86
|
+
mime && new(mime[0])
|
87
|
+
end
|
88
|
+
|
89
|
+
# Return type as string
|
90
|
+
def to_s
|
91
|
+
type
|
92
|
+
end
|
93
|
+
|
94
|
+
# Allow comparison with string
|
95
|
+
def eql?(other)
|
96
|
+
type == other.to_s
|
97
|
+
end
|
98
|
+
|
99
|
+
def hash
|
100
|
+
type.hash
|
101
|
+
end
|
102
|
+
|
103
|
+
alias == eql?
|
104
|
+
|
105
|
+
def self.child?(child, parent)
|
106
|
+
child == parent || TYPES.key?(child) && TYPES[child][1].any? {|p| child?(p, parent) }
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.magic_match_io(io, matches)
|
110
|
+
matches.any? do |offset, value, children|
|
111
|
+
match =
|
112
|
+
if Range === offset
|
113
|
+
io.seek(offset.begin)
|
114
|
+
x = io.read(offset.end - offset.begin + value.bytesize)
|
115
|
+
x && x.include?(value)
|
116
|
+
else
|
117
|
+
io.seek(offset)
|
118
|
+
io.read(value.bytesize) == value
|
119
|
+
end
|
120
|
+
match && (!children || magic_match_io(io, children))
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.magic_match_str(str, matches)
|
125
|
+
matches.any? do |offset, value, children|
|
126
|
+
match =
|
127
|
+
if Range === offset
|
128
|
+
x = str[offset.begin, offset.end - offset.begin + value.bytesize]
|
129
|
+
x && x.include?(value)
|
130
|
+
else
|
131
|
+
str[offset, value.bytesize] == value
|
132
|
+
end
|
133
|
+
match && (!children || magic_match_str(str, children))
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
private_class_method :magic_match_io, :magic_match_str
|
138
|
+
end
|