craftbook-nbt 1.0.0
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 +7 -0
- data/.gitignore +9 -0
- data/.yardopts +5 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +420 -0
- data/Rakefile +5 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/craftbook-nbt.gemspec +34 -0
- data/lib/craftbook/nbt/byte_array_tag.rb +37 -0
- data/lib/craftbook/nbt/byte_tag.rb +61 -0
- data/lib/craftbook/nbt/compound_tag.rb +32 -0
- data/lib/craftbook/nbt/container_tag.rb +55 -0
- data/lib/craftbook/nbt/double_tag.rb +40 -0
- data/lib/craftbook/nbt/enumerable_tag.rb +106 -0
- data/lib/craftbook/nbt/float_tag.rb +35 -0
- data/lib/craftbook/nbt/int_array_tag.rb +38 -0
- data/lib/craftbook/nbt/int_tag.rb +49 -0
- data/lib/craftbook/nbt/list_tag.rb +69 -0
- data/lib/craftbook/nbt/long_array_tag.rb +37 -0
- data/lib/craftbook/nbt/long_tag.rb +50 -0
- data/lib/craftbook/nbt/short_tag.rb +48 -0
- data/lib/craftbook/nbt/snbt/lexer.rb +161 -0
- data/lib/craftbook/nbt/snbt/snbt.rb +121 -0
- data/lib/craftbook/nbt/snbt/snbt.rex +74 -0
- data/lib/craftbook/nbt/snbt.rb +2 -0
- data/lib/craftbook/nbt/string_tag.rb +40 -0
- data/lib/craftbook/nbt/tag.rb +197 -0
- data/lib/craftbook/nbt/tag_builder.rb +220 -0
- data/lib/craftbook/nbt/value_tag.rb +51 -0
- data/lib/craftbook/nbt/version.rb +10 -0
- data/lib/craftbook/nbt.rb +298 -0
- metadata +112 -0
@@ -0,0 +1,121 @@
|
|
1
|
+
|
2
|
+
require_relative 'lexer'
|
3
|
+
|
4
|
+
module CraftBook
|
5
|
+
|
6
|
+
module NBT
|
7
|
+
|
8
|
+
##
|
9
|
+
# Parses a stringified NBT string and creates a {CompoundTag} from it.
|
10
|
+
#
|
11
|
+
# @param string_nbt [String] The stringified NBT code to parse.
|
12
|
+
#
|
13
|
+
# @raise [SyntaxError] When the source `string_nbt` is not valid S-NBT.
|
14
|
+
# @raise [ParseError] When a an incorrect value is specified for the type of tag it represents.
|
15
|
+
# @raise [ArgumentError] When `string_nbt` is `nil`
|
16
|
+
#
|
17
|
+
# @return [CompoundTag] The parsed {CompoundTag} instance.
|
18
|
+
#
|
19
|
+
# @note This method is not safe to call in parallel from multiple threads.
|
20
|
+
# @see https://minecraft.fandom.com/wiki/NBT_format#SNBT_format
|
21
|
+
def self.parse_snbt(string_nbt)
|
22
|
+
raise(ArgumentError, "input string cannot be nil or empty") if string_nbt.nil? || string_nbt.empty?
|
23
|
+
@pos = 0
|
24
|
+
@depth = 0
|
25
|
+
lexer = Tokenizer.new
|
26
|
+
@tokens = lexer.tokenize(string_nbt)
|
27
|
+
parse_object(@tokens.first)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def self.assert_type(expected, actual)
|
33
|
+
raise(SyntaxError, "expected #{expected} token, got #{actual}") unless expected == actual
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.parse_name(token)
|
37
|
+
assert_type(:IDENTIFIER, token.type)
|
38
|
+
assert_type(:SEPARATOR, move_next.type)
|
39
|
+
# move_next
|
40
|
+
token.value
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.parse_array(name, klass)
|
44
|
+
values = []
|
45
|
+
loop do
|
46
|
+
token = move_next
|
47
|
+
case token.type
|
48
|
+
when :END_ARRAY then break
|
49
|
+
when :COMMA then next
|
50
|
+
else values.push(token.value)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
klass.new(name, *values)
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.move_next
|
58
|
+
@pos += 1
|
59
|
+
token = @tokens[@pos] || raise(SyntaxError, 'unexpected end of input')
|
60
|
+
[:WHITESPACE, :COMMA].include?(token.type) ? move_next : token
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.parse_list(name)
|
64
|
+
values = []
|
65
|
+
types = []
|
66
|
+
loop do
|
67
|
+
token = move_next
|
68
|
+
case token.type
|
69
|
+
when :COMMA then next
|
70
|
+
when :END_ARRAY then break
|
71
|
+
else
|
72
|
+
types.push(token.type)
|
73
|
+
values.push(parse_object(token))
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
return ListTag.new(name, Tag::TYPE_END) if types.empty?
|
78
|
+
raise(ParseError, "lists must contain only the same child type") unless types.uniq.size <= 1
|
79
|
+
ListTag.new(name, values.first.type, *values)
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.parse_object(token)
|
83
|
+
|
84
|
+
name = nil
|
85
|
+
if token.type == :IDENTIFIER
|
86
|
+
name = parse_name(token)
|
87
|
+
token = move_next
|
88
|
+
end
|
89
|
+
|
90
|
+
case token.type
|
91
|
+
when :STRING then StringTag.new(name, token.value)
|
92
|
+
when :INT then IntTag.new(name, token.value)
|
93
|
+
when :DOUBLE then DoubleTag.new(name, token.value)
|
94
|
+
when :FLOAT then FloatTag.new(name, token.value)
|
95
|
+
when :BYTE then ByteTag.new(name, token.value)
|
96
|
+
when :SHORT then ShortTag.new(name, token.value)
|
97
|
+
when :LONG then LongTag.new(name, token.value)
|
98
|
+
when :BYTE_ARRAY then parse_array(name, ByteArrayTag)
|
99
|
+
when :INT_ARRAY then parse_array(name, IntArrayTag)
|
100
|
+
when :LONG_ARRAY then parse_array(name, LongArrayTag)
|
101
|
+
when :LIST_ARRAY then parse_list(name)
|
102
|
+
when :COMPOUND_BEGIN then parse_compound(token, name)
|
103
|
+
else raise(ParseError, "invalid token, expected object type, got :#{token.type}")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.parse_compound(token, name)
|
108
|
+
assert_type(:COMPOUND_BEGIN, token.type)
|
109
|
+
compound = CompoundTag.new(name)
|
110
|
+
|
111
|
+
loop do
|
112
|
+
token = move_next
|
113
|
+
break if token.type == :COMPOUND_END
|
114
|
+
next if token.type == :COMMA
|
115
|
+
compound.add(parse_object(token))
|
116
|
+
end
|
117
|
+
|
118
|
+
compound
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module CraftBook
|
2
|
+
module NBT
|
3
|
+
class Tokenizer
|
4
|
+
|
5
|
+
macro
|
6
|
+
nl \n|\r\n|\r|\f
|
7
|
+
w [\s]*
|
8
|
+
num -?([0-9]+|[0-9]*\.[0-9]+)
|
9
|
+
l_bracket \[
|
10
|
+
r_bracket \]
|
11
|
+
l_brace \{
|
12
|
+
r_brace \}
|
13
|
+
|
14
|
+
integer -?([0-9]+)
|
15
|
+
decimal -?[0-9]*\.[0-9]+
|
16
|
+
escape {unicode}|\\[^\n\r\f0-9A-Fa-f]
|
17
|
+
id [A-Za-z0-9-_]
|
18
|
+
rule
|
19
|
+
\{{w} { [:COMPOUND_BEGIN] }
|
20
|
+
{w}\} { [:COMPOUND_END] }
|
21
|
+
|
22
|
+
".+?"(?=:) { [:IDENTIFIER, text.gsub!(/\A"|"\Z/, '') ] }
|
23
|
+
'.+?'(?=:) { [:IDENTIFIER, text.gsub!(/\A'|'\Z/, '') ] }
|
24
|
+
[A-Za-z0-9_-]+?(?=:) { [:IDENTIFIER, text] }
|
25
|
+
".*?" { [:STRING, text.gsub!(/\A"|"\Z/, '') ] }
|
26
|
+
'.*?' { [:STRING, text.gsub!(/\A'|'\Z/, '') ] }
|
27
|
+
|
28
|
+
# Control Characters
|
29
|
+
{w}:{w} { [:SEPARATOR, text] }
|
30
|
+
{w},{w} { [:COMMA, text] }
|
31
|
+
|
32
|
+
# Collection Types
|
33
|
+
|
34
|
+
{l_bracket}B;{w}? { [:BYTE_ARRAY, text] }
|
35
|
+
{l_bracket}I;{w}? { [:INT_ARRAY, text] }
|
36
|
+
{l_bracket}L;{w}? { [:LONG_ARRAY, text] }
|
37
|
+
\[{w}? { [:LIST_ARRAY, text] }
|
38
|
+
{w}\] { [:END_ARRAY, text] }
|
39
|
+
|
40
|
+
# Numeric Types
|
41
|
+
{decimal}[Ff] { [:FLOAT, text.chop.to_f ] }
|
42
|
+
{decimal}[Dd]? { [:DOUBLE, text.tr('Dd', '').to_f ] }
|
43
|
+
{integer}[Bb] { [:BYTE, text.chop.to_i ] }
|
44
|
+
{integer}[Ss] { [:SHORT, text.chop.to_i ] }
|
45
|
+
{integer}[Ll] { [:LONG, text.chop.to_i ] }
|
46
|
+
{integer} { [:INT, text.to_i ] }
|
47
|
+
|
48
|
+
[\s]+ { [:WHITESPACE, text] }
|
49
|
+
[\S]+ { [:STRING, text] }
|
50
|
+
. { [:CHAR, text] }
|
51
|
+
|
52
|
+
inner
|
53
|
+
|
54
|
+
Token = Struct.new(:type, :value)
|
55
|
+
|
56
|
+
def tokenize(code)
|
57
|
+
scan_setup(code)
|
58
|
+
if block_given?
|
59
|
+
while token = next_token
|
60
|
+
yield Token.new(*token)
|
61
|
+
end
|
62
|
+
return self
|
63
|
+
end
|
64
|
+
tokens = []
|
65
|
+
while token = next_token
|
66
|
+
tokens << Token.new(*token)
|
67
|
+
end
|
68
|
+
tokens
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
|
2
|
+
module CraftBook
|
3
|
+
|
4
|
+
module NBT
|
5
|
+
|
6
|
+
##
|
7
|
+
# Represents a UTF-8 encoded string.
|
8
|
+
class StringTag < ValueTag
|
9
|
+
|
10
|
+
##
|
11
|
+
# @!attribute [rw] value
|
12
|
+
# @return [String] the value of the tag.
|
13
|
+
|
14
|
+
##
|
15
|
+
# Creates a new instance of the {StringTag} class.
|
16
|
+
#
|
17
|
+
# @param name [String,NilClass] The name of the tag, or `nil` when unnamed.
|
18
|
+
# @param value [String,NilClass] The value of the tag.
|
19
|
+
def initialize(name, value = '')
|
20
|
+
super(TYPE_STRING, name, value)
|
21
|
+
end
|
22
|
+
|
23
|
+
def value=(value)
|
24
|
+
@value = String(value)
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# @return [String] the NBT tag as a formatted and human-readable string.
|
29
|
+
def to_s
|
30
|
+
"TAG_String(#{@name ? "\"#{@name}\"" : 'None'}): \"#{@value}\""
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# @return [String] the NBT tag as an SNBT string.
|
35
|
+
def stringify
|
36
|
+
"#{snbt_prefix}\"#{@value}\""
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
|
2
|
+
module CraftBook
|
3
|
+
module NBT
|
4
|
+
|
5
|
+
##
|
6
|
+
# @abstract
|
7
|
+
# Abstract base class for all tag types.
|
8
|
+
class Tag
|
9
|
+
|
10
|
+
##
|
11
|
+
# Not a concrete tag, implies the end of a Compound tag during serialization.
|
12
|
+
TYPE_END = 0x00
|
13
|
+
|
14
|
+
##
|
15
|
+
# A signed 8-bit integer in the range of `-128` to `127` inclusive.
|
16
|
+
TYPE_BYTE = 0x01
|
17
|
+
|
18
|
+
##
|
19
|
+
# A signed 16-bit integer in the range of `-32768` to `32767` inclusive.
|
20
|
+
TYPE_SHORT = 0x02
|
21
|
+
|
22
|
+
##
|
23
|
+
# A signed 32-bit integer in the range of `-2147483648` to `2147483647` inclusive.
|
24
|
+
TYPE_INT = 0x03
|
25
|
+
|
26
|
+
##
|
27
|
+
# A signed 64-bit integer in the range of `-9223372036854775808` and `9223372036854775807` inclusive.
|
28
|
+
TYPE_LONG = 0x04
|
29
|
+
|
30
|
+
##
|
31
|
+
# An IEEE-754 single-precision floating point number (NaN possible).
|
32
|
+
TYPE_FLOAT = 0x05
|
33
|
+
|
34
|
+
##
|
35
|
+
# An IEEE-754 double-precision floating point number (NaN possible).
|
36
|
+
TYPE_DOUBLE = 0x06
|
37
|
+
|
38
|
+
##
|
39
|
+
# A contiguous collection of signed 8-bit integers in the range of `-128` to `127` inclusive.
|
40
|
+
TYPE_BYTE_ARRAY = 0x07
|
41
|
+
|
42
|
+
##
|
43
|
+
# A UTF-8 encoded string.
|
44
|
+
TYPE_STRING = 0x08
|
45
|
+
|
46
|
+
##
|
47
|
+
# A collection of **unnamed** tags of the same type.
|
48
|
+
TYPE_LIST = 0x09
|
49
|
+
|
50
|
+
##
|
51
|
+
# A collection of **named** tags, order not guaranteed.
|
52
|
+
TYPE_COMPOUND = 0x0A
|
53
|
+
|
54
|
+
##
|
55
|
+
# A contiguous collection of signed 32-bit integers in the range of `-2147483648` to `2147483647` inclusive.
|
56
|
+
TYPE_INT_ARRAY = 0x0B
|
57
|
+
|
58
|
+
##
|
59
|
+
# A contiguous collection of signed 64-bit integers in the range of `-9223372036854775808`
|
60
|
+
# and `9223372036854775807` inclusive.
|
61
|
+
TYPE_LONG_ARRAY = 0x0C
|
62
|
+
|
63
|
+
##
|
64
|
+
# @return [Integer] one of the `TYPE_*` constants to describe the primitive NBT type.
|
65
|
+
attr_reader :type
|
66
|
+
|
67
|
+
##
|
68
|
+
# @return [String?] the name of the tag, or `nil` if unnamed.
|
69
|
+
attr_reader :name
|
70
|
+
|
71
|
+
##
|
72
|
+
# Creates a new instance of the {Tag} class.
|
73
|
+
# @param type [Integer] One of the `TAG_*` constants indicating the primitive tag type.
|
74
|
+
# @param name [String,NilClass] The name of the tag, or `nil` when unnamed.
|
75
|
+
def initialize(type, name)
|
76
|
+
@type = type || raise(TypeError, 'type cannot be nil')
|
77
|
+
@name = name
|
78
|
+
end
|
79
|
+
|
80
|
+
##
|
81
|
+
# Sets the name of the tag.
|
82
|
+
# @param value [String] The value to set the tag name as.
|
83
|
+
# @return [String?] The name of the tag.
|
84
|
+
def name=(value)
|
85
|
+
@name = value.nil? ? nil : String(value)
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# @abstract
|
90
|
+
# @return [Hash{Symbol => Object}] the hash-representation of this object.
|
91
|
+
def to_h
|
92
|
+
{ name: @name, type: @type }
|
93
|
+
end
|
94
|
+
|
95
|
+
##
|
96
|
+
# Retrieves the NBT tag in JavaScript Object Notation (JSON) format.
|
97
|
+
#
|
98
|
+
# @param pretty [Boolean] Flag indicating if output should be formatted in a more human-readable structure.
|
99
|
+
# @param opts [{Symbol=>String}] Options for how the output is formatted when using `pretty` flag.
|
100
|
+
# @option opts [String] indent: (' ') The string used for indenting.
|
101
|
+
# @option opts [String] space: (' ') The string used for spaces.
|
102
|
+
# @option opts [String] array_nl: ("\n") The string used for newlines between array elements.
|
103
|
+
# @option opts [String] object_nl: ("\n") The string used for newlines between objects.
|
104
|
+
#
|
105
|
+
# @return [String] the JSON representation of this object.
|
106
|
+
def to_json(pretty = false, **opts)
|
107
|
+
pretty ? JSON.pretty_generate(to_h.compact, **opts) : to_h.compact.to_json
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# Parses a {Tag} object from a JSON string.
|
112
|
+
#
|
113
|
+
# @param json [String] A string in JSON format.
|
114
|
+
# @return [Tag] The deserialized {Tag} instance.
|
115
|
+
def self.parse(json)
|
116
|
+
hash = JSON.parse(json, symbolize_names: true )
|
117
|
+
raise(ParseError, 'invalid format, expected object') unless hash.is_a?(Hash)
|
118
|
+
from_hash(hash)
|
119
|
+
end
|
120
|
+
|
121
|
+
##
|
122
|
+
# @abstract
|
123
|
+
# @raise [NotImplementedError] Method must be overridden in derived classes.
|
124
|
+
# @return [String] the NBT tag as an SNBT string.
|
125
|
+
def stringify
|
126
|
+
raise(NotImplementedError, "#{__method__} must be implemented in derived classes")
|
127
|
+
end
|
128
|
+
|
129
|
+
alias_method :snbt, :stringify
|
130
|
+
alias_method :to_hash, :to_h
|
131
|
+
alias_method :to_str, :to_s
|
132
|
+
|
133
|
+
##
|
134
|
+
# Retrieves the NBT tag as a formatted and tree-structured string.
|
135
|
+
#
|
136
|
+
# @param indent [String] The string inserted for each level of indent.
|
137
|
+
#
|
138
|
+
# @see pretty_print
|
139
|
+
# @return [String] The NBT string as a formatted string.
|
140
|
+
def pretty(indent = ' ')
|
141
|
+
io = StringIO.new
|
142
|
+
pretty_print(io, 0, indent)
|
143
|
+
io.string
|
144
|
+
end
|
145
|
+
|
146
|
+
##
|
147
|
+
# Outputs the NBT tag as a formatted and tree-structured string.
|
148
|
+
#
|
149
|
+
# @param io [IO,#puts] An IO-like object that responds to #puts.
|
150
|
+
# @param level [Integer] The indentation level.
|
151
|
+
# @param indent [String] The string inserted for each level of indent.
|
152
|
+
#
|
153
|
+
# @see pretty
|
154
|
+
# @return [void]
|
155
|
+
def pretty_print(io = STDOUT, level = 0, indent = ' ')
|
156
|
+
io.puts(indent * level + self.to_s)
|
157
|
+
end
|
158
|
+
|
159
|
+
protected
|
160
|
+
|
161
|
+
def snbt_prefix
|
162
|
+
@name ? "#{@name}:" : ''
|
163
|
+
end
|
164
|
+
|
165
|
+
def self.class_from_type(type)
|
166
|
+
case type
|
167
|
+
when Tag::TYPE_BYTE then ByteTag
|
168
|
+
when Tag::TYPE_SHORT then ShortTag
|
169
|
+
when Tag::TYPE_INT then IntTag
|
170
|
+
when Tag::TYPE_LONG then LongTag
|
171
|
+
when Tag::TYPE_FLOAT then FloatTag
|
172
|
+
when Tag::TYPE_DOUBLE then DoubleTag
|
173
|
+
when Tag::TYPE_BYTE_ARRAY then ByteArrayTag
|
174
|
+
when Tag::TYPE_STRING then StringTag
|
175
|
+
when Tag::TYPE_LIST then ListTag
|
176
|
+
when Tag::TYPE_COMPOUND then CompoundTag
|
177
|
+
when Tag::TYPE_INT_ARRAY then IntArrayTag
|
178
|
+
when Tag::TYPE_LONG_ARRAY then LongArrayTag
|
179
|
+
else raise(ParseError, "invalid tag type")
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def self.from_hash(hash)
|
184
|
+
|
185
|
+
name = hash[:name]
|
186
|
+
type = hash[:type]
|
187
|
+
raise(ParseError, "invalid type") unless !type.nil? & type.between?(TYPE_END, TYPE_LONG_ARRAY)
|
188
|
+
|
189
|
+
tag = class_from_type(type).allocate
|
190
|
+
tag.instance_variable_set(:@name, name)
|
191
|
+
tag.instance_variable_set(:@type, type)
|
192
|
+
tag.send(:parse_hash, hash)
|
193
|
+
tag
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
@@ -0,0 +1,220 @@
|
|
1
|
+
|
2
|
+
module CraftBook
|
3
|
+
module NBT
|
4
|
+
|
5
|
+
##
|
6
|
+
# Provides an intuitive and simplified way of building a complete NBT document from scratch, using only basic
|
7
|
+
# values without the need of creating intermediate {Tag} objects.
|
8
|
+
class TagBuilder
|
9
|
+
|
10
|
+
##
|
11
|
+
# @return [CompoundTag] the implicit top-level {CompoundTag} that the {TagBuilder} is creating.
|
12
|
+
attr_reader :root
|
13
|
+
|
14
|
+
##
|
15
|
+
# Creates a new instance of the {TagBuilder} class.
|
16
|
+
# @param name [String,NilClass] the name of the implicit top-level {CompoundTag} being created.
|
17
|
+
def initialize(name)
|
18
|
+
@root = CompoundTag.new(name)
|
19
|
+
@stack = []
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# Creates a new {TagBuilder} instance within a block, returning the completed {CompoundTag} when the block
|
24
|
+
# closes.
|
25
|
+
#
|
26
|
+
# @param name [String,NilClass] the name of the implicit top-level {CompoundTag} that the {TagBuilder} is creating.
|
27
|
+
# @return [CompoundTag] The resulting {CompoundTag} that was created.
|
28
|
+
# @raise [LocalJumpError] when called without a block.
|
29
|
+
def self.create(name)
|
30
|
+
raise(LocalJumpError, 'block required') unless block_given?
|
31
|
+
builder = new(name)
|
32
|
+
yield builder
|
33
|
+
builder.result
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Creates a new {TagBuilder} instance from an existing {CompoundTag}.
|
38
|
+
# @param compound_tag [CompoundTag] An existing {CompoundTag} instance.
|
39
|
+
# @return [TagBuilder] A newly created {TagBuilder}.
|
40
|
+
# @raise [TypeError] when `compound_tag` is not a {CompoundTag}.
|
41
|
+
def self.from(compound_tag)
|
42
|
+
raise(TypeError, "#{compound_tag} is not a #{CompoundTag}") unless compound_tag.is_a?(CompoundTag)
|
43
|
+
|
44
|
+
builder = allocate
|
45
|
+
builder.instance_variable_set(:@root, compound_tag)
|
46
|
+
builder.instance_variable_set(:@stack, Array.new)
|
47
|
+
builder
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# Adds an existing {Tag} instance as a child to the current node.
|
52
|
+
# @param tag [Tag] The {Tag} object to add.
|
53
|
+
#
|
54
|
+
# @yieldparam builder [TagBuilder] Yields the {TagBuilder} instance to the block.
|
55
|
+
# @return [self]
|
56
|
+
# @raise [TypeError] when `tag` is not a {Tag} instance or `nil`.
|
57
|
+
def add(tag)
|
58
|
+
raise(TypeError, "tag cannot be nil") unless tag.is_a?(Tag)
|
59
|
+
|
60
|
+
root = @stack.empty? ? @root : @stack.last
|
61
|
+
|
62
|
+
if root.is_a?(CompoundTag) && tag.name.nil?
|
63
|
+
warn("direct children of Compound tags must be named")
|
64
|
+
elsif root.is_a?(ListTag) && tag.name
|
65
|
+
tag.name = nil
|
66
|
+
end
|
67
|
+
root.push(tag)
|
68
|
+
self
|
69
|
+
end
|
70
|
+
|
71
|
+
alias_method :<<, :add
|
72
|
+
alias_method :push, :add
|
73
|
+
|
74
|
+
##
|
75
|
+
# Creates a {ByteTag} from the specified value, and adds it to the current node.
|
76
|
+
# @param value [Integer] The value of the tag.
|
77
|
+
# @param name [String,NilClass] The name of the tag, or `nil` when adding to a {ListTag} node.
|
78
|
+
# @return [self]
|
79
|
+
def byte(name, value)
|
80
|
+
add(ByteTag.new(name, Integer(value)))
|
81
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# Creates a {ShortTag} from the specified value, and adds it to the current node.
|
85
|
+
# @param value [Integer] The value of the tag.
|
86
|
+
# @param name [String,NilClass] The name of the tag, or `nil` when adding to a {ListTag} node.
|
87
|
+
# @return [self]
|
88
|
+
def short(name, value)
|
89
|
+
add(ShortTag.new(name, Integer(value)))
|
90
|
+
end
|
91
|
+
|
92
|
+
##
|
93
|
+
# Creates a {IntTag} from the specified value, and adds it to the current node.
|
94
|
+
# @param value [Integer] The value of the tag.
|
95
|
+
# @param name [String,NilClass] The name of the tag, or `nil` when adding to a {ListTag} node.
|
96
|
+
# @return [self]
|
97
|
+
def int(name, value)
|
98
|
+
add(IntTag.new(name, Integer(value)))
|
99
|
+
end
|
100
|
+
|
101
|
+
##
|
102
|
+
# Creates a {LongTag} from the specified value, and adds it to the current node.
|
103
|
+
# @param value [Integer] The value of the tag.
|
104
|
+
# @param name [String,NilClass] The name of the tag, or `nil` when adding to a {ListTag} node.
|
105
|
+
# @return [self]
|
106
|
+
def long(name, value)
|
107
|
+
add(LongTag.new(name, Integer(value)))
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# Creates a {FloatTag} from the specified value, and adds it to the current node.
|
112
|
+
# @param name [String,NilClass] The name of the tag, or `nil` when adding to a {ListTag} node.
|
113
|
+
# @param value [Float] The value of the tag.
|
114
|
+
# @return [self]
|
115
|
+
def float(name, value)
|
116
|
+
add(FloatTag.new(name, Float(value)))
|
117
|
+
end
|
118
|
+
|
119
|
+
##
|
120
|
+
# Creates a {DoubleTag} from the specified value, and adds it to the current node.
|
121
|
+
# @param name [String,NilClass] The name of the tag, or `nil` when adding to a {ListTag} node.
|
122
|
+
# @param value [Float] The value of the tag.
|
123
|
+
# @return [self]
|
124
|
+
def double(name, value)
|
125
|
+
add(DoubleTag.new(name, Float(value)))
|
126
|
+
end
|
127
|
+
|
128
|
+
##
|
129
|
+
# Creates a {StringTag} from the specified value, and adds it to the current node.
|
130
|
+
# @param value [String,Object] The value of the tag.
|
131
|
+
# @param name [String,NilClass] The name of the tag, or `nil` when adding to a {ListTag} node.
|
132
|
+
# @return [self]
|
133
|
+
def string(name, value)
|
134
|
+
add(StringTag.new(name, String(value)))
|
135
|
+
end
|
136
|
+
|
137
|
+
##
|
138
|
+
# Creates a {ByteArrayTag} from the specified values, and adds it to the current node.
|
139
|
+
# @param values [Array<Integer>,Enumerable] The child values of the tag.
|
140
|
+
# @param name [String,NilClass] The name of the tag, or `nil` when adding to a {ListTag} node.
|
141
|
+
# @return [self]
|
142
|
+
def byte_array(name, *values)
|
143
|
+
add(ByteArrayTag.new(name, *values))
|
144
|
+
end
|
145
|
+
|
146
|
+
##
|
147
|
+
# Creates a {IntArrayTag} from the specified values, and adds it to the current node.
|
148
|
+
# @param values [Array<Integer>,Enumerable] The child values of the tag.
|
149
|
+
# @param name [String,NilClass] The name of the tag, or `nil` when adding to a {ListTag} node.
|
150
|
+
# @return [self]
|
151
|
+
def int_array(name, *values)
|
152
|
+
add(IntArrayTag.new(name, *values))
|
153
|
+
end
|
154
|
+
|
155
|
+
##
|
156
|
+
# Creates a {LongArrayTag} from the specified values, and adds it to the current node.
|
157
|
+
# @param values [Array<Integer>,Enumerable] The child values of the tag.
|
158
|
+
# @param name [String,NilClass] The name of the tag, or `nil` when adding to a {ListTag} node.
|
159
|
+
# @return [self]
|
160
|
+
def long_array(name, *values)
|
161
|
+
add(LongArrayTag.new(name, *values))
|
162
|
+
end
|
163
|
+
|
164
|
+
##
|
165
|
+
# Creates a {ListTag} from the specified value, and adds it to the current node.
|
166
|
+
#
|
167
|
+
# @param child_type [Integer] One of the `Tag::TYPE_*` constants indicating the type of children in this tag.
|
168
|
+
# @param name [String,NilClass] The name of the tag, or `nil` when adding to a {ListTag} node.
|
169
|
+
# @param children [Array<Tag>,Enumerable] The child values of the tag.
|
170
|
+
#
|
171
|
+
# @overload list(child_type, name = nil, children =nil, &block)
|
172
|
+
# When called with a block, creates a new node that is pushed onto the stack. All tags created within the
|
173
|
+
# block will be added to this new scope. The node is closed when the block exits.
|
174
|
+
# @yield Yields nothing to the block.
|
175
|
+
#
|
176
|
+
# @overload list(child_type, name = nil, children =nil)
|
177
|
+
# When called without a block, all values to be included must be present in the `children` argument.
|
178
|
+
#
|
179
|
+
# @return [self]
|
180
|
+
def list(name, child_type, *children)
|
181
|
+
list = ListTag.new(name, child_type, *children)
|
182
|
+
|
183
|
+
if block_given?
|
184
|
+
@stack.push(list)
|
185
|
+
yield
|
186
|
+
@stack.pop
|
187
|
+
end
|
188
|
+
|
189
|
+
add(list)
|
190
|
+
end
|
191
|
+
|
192
|
+
##
|
193
|
+
# Creates a {CompoundTag} from the specified value, and adds it to the current node.
|
194
|
+
#
|
195
|
+
# @param name [String,NilClass] The name of the tag, or `nil` when adding to a {ListTag} node.
|
196
|
+
# @param children [Array<Tag>,Enumerable] The child values of the tag.
|
197
|
+
#
|
198
|
+
# @overload compound(name = nil, children =nil, &block)
|
199
|
+
# When called with a block, creates a new node that is pushed onto the stack. All tags created within the
|
200
|
+
# block will be added to this new scope. The node is closed when the block exits.
|
201
|
+
# @yield Yields nothing to the block.
|
202
|
+
#
|
203
|
+
# @overload compound(name = nil, children =nil)
|
204
|
+
# When called without a block, all values to be included must be present in the `children` argument.
|
205
|
+
#
|
206
|
+
# @return [self]
|
207
|
+
def compound(name, *children)
|
208
|
+
compound = CompoundTag.new(name, *children)
|
209
|
+
|
210
|
+
if block_given?
|
211
|
+
@stack.push(compound)
|
212
|
+
yield self
|
213
|
+
@stack.pop
|
214
|
+
end
|
215
|
+
|
216
|
+
add(compound)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|