-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathsb3_parser.rb
More file actions
76 lines (62 loc) · 1.88 KB
/
Copy pathsb3_parser.rb
File metadata and controls
76 lines (62 loc) · 1.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# frozen_string_literal: true
require 'json'
require 'marcel'
require 'stringio'
require 'zip'
class Sb3Parser
class MissingProjectJsonError < StandardError; end
class MissingAssetError < StandardError; end
attr_reader :component, :file_path, :io
def initialize(component: nil, file_path: nil)
@component = component
@file_path = component&.fetch(:file_path, nil) || file_path
@io = component&.fetch(:io, nil)
end
def parse
open_zip do |zip_file|
project_json = project_json_entry(zip_file)
content = JSON.parse(project_json.get_input_stream.read)
output = {
scratch_component: { content: },
assets: assets(zip_file, extract_asset_names(content))
}
output
end
end
private
def open_zip(&)
return Zip::File.open(file_path, &) if file_path
io.rewind if io.respond_to?(:rewind)
result = nil
Zip::File.open_buffer(io.read) { |zip_file| result = yield zip_file }
result
end
def project_json_entry(zip_file)
zip_file.find_entry('project.json') || raise(MissingProjectJsonError, 'project.json not found in SB3 archive')
end
def extract_asset_names(value)
case value
when Hash
names = []
names << value['md5ext'] if value['md5ext'].is_a?(String)
value.each_value { |item| names.concat(extract_asset_names(item)) }
names.uniq
when Array
value.flat_map { |item| extract_asset_names(item) }.uniq
else
[]
end
end
def assets(zip_file, asset_names)
asset_names.map do |asset_name|
entry = zip_file.find_entry(asset_name) || raise(MissingAssetError, "asset #{asset_name} not found in SB3 archive")
asset(entry)
end
end
def asset(entry)
io = StringIO.new(entry.get_input_stream.read)
content_type = Marcel::MimeType.for(io, name: entry.name)
io.rewind
{ filename: entry.name, io:, content_type: }
end
end