craftbook-nbt 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|