blobsterix 0.0.9
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/.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
|