{"id":22349101,"url":"https://github.com/foreverzer0/craftbook-nbt","last_synced_at":"2025-10-19T00:42:48.376Z","repository":{"id":62556345,"uuid":"400966635","full_name":"ForeverZer0/craftbook-nbt","owner":"ForeverZer0","description":"Feature-rich Ruby implementation of the Named Binary Tag (NBT) specification.","archived":false,"fork":false,"pushed_at":"2021-08-31T00:10:38.000Z","size":31,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-10-28T09:31:52.469Z","etag":null,"topics":["minecraft","named-binary-tag","nbt","nbt-format","nbt-library","nbt-parser","snbt"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ForeverZer0.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-08-29T06:17:49.000Z","updated_at":"2022-07-13T13:00:24.000Z","dependencies_parsed_at":"2022-11-03T05:45:48.224Z","dependency_job_id":null,"html_url":"https://github.com/ForeverZer0/craftbook-nbt","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ForeverZer0%2Fcraftbook-nbt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ForeverZer0%2Fcraftbook-nbt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ForeverZer0%2Fcraftbook-nbt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ForeverZer0%2Fcraftbook-nbt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ForeverZer0","download_url":"https://codeload.github.com/ForeverZer0/craftbook-nbt/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228098354,"owners_count":17869032,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["minecraft","named-binary-tag","nbt","nbt-format","nbt-library","nbt-parser","snbt"],"created_at":"2024-12-04T11:07:21.175Z","updated_at":"2025-10-19T00:42:43.330Z","avatar_url":"https://github.com/ForeverZer0.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# CraftBook::NBT\n\nA feature-rich and complete Ruby implementation of the Named Binary Tag (NBT) format. While it is an integral part of\nthe broader CraftBook API, it is an independent module with no dependencies, and can be used for any purpose where\nreading/writing/converting the NBT format is required.\n\n# Features\n\n* Intuitive and simple to use, with a user-friendly API surface\n* Reads from any IO-like object\n* `TagBuilder` class for easily building complete NBT documents from scratch (see example below)\n* Conversion to and from JSON\n* Conversion to and from SNBT (aka _stringified_ NBT), performed properly with a grammar and a lexical parser with `racc` (standard library)\n* Custom formatted output in a tree structure for simple viewing NBT, or debugging for correctness\n* Automatic compression detection\n* Well-structured and logical inheritance tree\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'craftbook-nbt'\n```\n\nAnd then execute:\n\n    $ bundle install\n\nOr install it yourself as:\n\n    $ gem install craftbook-nbt\n\n## Usage\n\n### Require\n\nAfter installing the gem, everything can be made available by requiring one file:\n\n```ruby\nrequire 'craftbook/nbt'\n```\n\n### Reading\n\nFor the purpose of example, we will use the de-facto [bigtest.nbt](https://raw.github.com/Dav1dde/nbd/master/test/bigtest.nbt) \nfor NBT as recommended by https://wiki.vg/NBT where the specification is outlined.\n\nTo load a file (GZip and ZLib compressed files will be detected and handled automatically.):\n```ruby\ntag = CraftBook::NBT.read_file('/path/to/bigtest.nbt')\n```\n\nThe resulting object is a `CompoundTag` instance, which is the implicit top-level container of all files. This tag\n(among others) implements the `Enumerable` mixin, and exhibits typical array-like behavior, including accessing by\nindex for inserting/removing/fetching child elements.\n\nIf you need to read from an existing IO-like object other than a file, use the following:\n```ruby\ntag = CraftBook::NBT.read(io)\n```\n\nUnlike a file, compression cannot be detected from a stream, as not all streams support seeking (i.e. network). \nFurthermore, compression algorithms typically cannot start from an unknown position in a stream, and it is unsafe to\nassume the position for each use. For this reason, it is up to users to apply any needed decompression wrappers over the\nIO object before passing to this method. Any object that responds to `#read` and returns a String is a viable\nparameter that can be used.\n\n### Writing\n\nWriting is just as simple:\n```ruby\nCraftBook::NBT.write_file('/path/to/file.nbt', compression: :gzip, level: :optimal)\n```\n\n...or if writing directly to an `IO` object or one that implements `#write`:\n```ruby\ncompound_tag = CompoundTag.new(\"My First NBT Tag!\")\ncompound_tag.push(StringTag.new(\"Hello\", \"World\"))\n\nbytes_written = CraftBook::NBT.write(STDOUT, compound_tag, compression: :zlib, level: :fastest)\n```\n\nCompression is optional, but defaults to GZip with \"default\" level when not specified. \n\n### Creating Tags\n\nTag creation can be done manually by creating individual tags and building the document manually, or you can use the\nincluded `TagBuilder` class to ease in their creation.\n\nFor a complete example, we will re-create the \"bigtest.nbt\" mentioned above from scratch, which uses all tag types:\n\n```ruby\ninclude CraftBook::NBT\n\ntb = TagBuilder.new(\"Level\")\ntb.compound('nested compound test') do\n\n  tb.compound('egg') do\n    tb.string('name', 'Eggburt')\n    tb.float('value', 0.5)\n  end\n\n  tb.compound(\"ham\") do\n    tb.string('name', 'Hampus')\n    tb.float('value', 0.75)\n  end\n\n  tb.int('intTest', 2147483647)\n  tb.byte('byteTest', 127)\n  tb.string('stringTest', \"HELLO WORLD THIS IS A TEST STRING \\u{c5}\\u{c4}\\u{d6}!\")\n\n  tb.list('listTest (long)', Tag::TYPE_LONG) do\n    tb.long(nil, 11)\n    tb.long(nil, 12)\n    tb.long(nil, 13)\n    tb.long(nil, 14)\n    tb.long(nil, 15)\n  end\n\n  tb.double('doubleTest', 0.49312871321823148)\n  tb.float('floatTest', 0.49823147058486938)\n  tb.long('longTest', 9223372036854775807)\n\n  tb.list('listTest (compound', Tag::TYPE_COMPOUND) do\n    tb.compound(nil) do\n      tb.long('created-on', 1264099775885)\n      tb.string('name', 'Compound tag #0')\n    end\n    tb.compound(nil) do\n      tb.long('created-on', 1264099775885)\n      tb.string('name', 'Compound tag #1')\n    end\n  end\n\n  name = 'byteArrayTest (the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...))'\n  array = (0...1000).map { |n| (n * n * 255 + n * 7) % 100 }\n  tb.byte_array(name, *array)\n  tb.short('shortTest', 32767)\nend\n\ntag = tb.root\n```\n\nWe can then compare the output:\n\n```ruby\ntag.pretty_print(STDOUT)\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eClick to Expand Output\u003c/summary\u003e\n\n```\nTAG_Compound(\"Level\"): 1 child\n{\n    TAG_Compound(\"nested compound test\"): 12 children\n    {\n        TAG_Compound(\"egg\"): 2 children\n        {\n            TAG_String(\"name\"): \"Eggburt\"\n            TAG_Float(\"value\"): 0.5\n        }\n        TAG_Compound(\"ham\"): 2 children\n        {\n            TAG_String(\"name\"): \"Hampus\"\n            TAG_Float(\"value\"): 0.75\n        }\n        TAG_Int(\"intTest\"): 2147483647\n        TAG_Byte(\"byteTest\"): 127\n        TAG_String(\"stringTest\"): \"HELLO WORLD THIS IS A TEST STRING ÅÄÖ!\"\n        TAG_List(\"listTest (long)\"): 5 children\n        {\n            TAG_Long(None): 11\n            TAG_Long(None): 12\n            TAG_Long(None): 13\n            TAG_Long(None): 14\n            TAG_Long(None): 15\n        }\n        TAG_Double(\"doubleTest\"): 0.4931287132182315\n        TAG_Float(\"floatTest\"): 0.4982314705848694\n        TAG_Long(\"longTest\"): 9223372036854775807\n        TAG_List(\"listTest (compound\"): 2 children\n        {\n            TAG_Compound(None): 2 children\n            {\n                TAG_Long(\"created-on\"): 1264099775885\n                TAG_String(\"name\"): \"Compound tag #0\"\n            }\n            TAG_Compound(None): 2 children\n            {\n                TAG_Long(\"created-on\"): 1264099775885\n                TAG_String(\"name\"): \"Compound tag #1\"\n            }\n        }\n        TAG_Byte_Array(\"byteArrayTest (the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...))\"): 1 item\n        TAG_Short(\"shortTest\"): 32767\n    }\n}\n```\n\n\u003c/details\u003e\n\nOr if you prefer JSON...\n\n```ruby\npretty = true\ntag.to_json(pretty, indent: '  ')\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eClick to expand JSON output\u003c/summary\u003e\n\n```json\n\n{\n  \"name\": \"Level\",\n  \"type\": 10,\n  \"values\": [\n    {\n      \"name\": \"nested compound test\",\n      \"type\": 10,\n      \"values\": [\n        {\n          \"name\": \"egg\",\n          \"type\": 10,\n          \"values\": [\n            {\n              \"name\": \"name\",\n              \"type\": 8,\n              \"value\": \"Eggburt\"\n            },\n            {\n              \"name\": \"value\",\n              \"type\": 5,\n              \"value\": 0.5\n            }\n          ]\n        },\n        {\n          \"name\": \"ham\",\n          \"type\": 10,\n          \"values\": [\n            {\n              \"name\": \"name\",\n              \"type\": 8,\n              \"value\": \"Hampus\"\n            },\n            {\n              \"name\": \"value\",\n              \"type\": 5,\n              \"value\": 0.75\n            }\n          ]\n        },\n        {\n          \"name\": \"intTest\",\n          \"type\": 3,\n          \"value\": 2147483647\n        },\n        {\n          \"name\": \"byteTest\",\n          \"type\": 1,\n          \"value\": 127\n        },\n        {\n          \"name\": \"stringTest\",\n          \"type\": 8,\n          \"value\": \"HELLO WORLD THIS IS A TEST STRING ÅÄÖ!\"\n        },\n        {\n          \"name\": \"listTest (long)\",\n          \"type\": 9,\n          \"child_type\": 4,\n          \"values\": [\n            {\n              \"value\": 11\n            },\n            {\n              \"value\": 12\n            },\n            {\n              \"value\": 13\n            },\n            {\n              \"value\": 14\n            },\n            {\n              \"value\": 15\n            }\n          ]\n        },\n        {\n          \"name\": \"doubleTest\",\n          \"type\": 6,\n          \"value\": 0.4931287132182315\n        },\n        {\n          \"name\": \"floatTest\",\n          \"type\": 5,\n          \"value\": 0.4982314705848694\n        },\n        {\n          \"name\": \"longTest\",\n          \"type\": 4,\n          \"value\": 9223372036854775807\n        },\n        {\n          \"name\": \"listTest (compound\",\n          \"type\": 9,\n          \"child_type\": 10,\n          \"values\": [\n            {\n              \"values\": [\n                {\n                  \"name\": \"created-on\",\n                  \"type\": 4,\n                  \"value\": 1264099775885\n                },\n                {\n                  \"name\": \"name\",\n                  \"type\": 8,\n                  \"value\": \"Compound tag #0\"\n                }\n              ]\n            },\n            {\n              \"values\": [\n                {\n                  \"name\": \"created-on\",\n                  \"type\": 4,\n                  \"value\": 1264099775885\n                },\n                {\n                  \"name\": \"name\",\n                  \"type\": 8,\n                  \"value\": \"Compound tag #1\"\n                }\n              ]\n            }\n          ]\n        },\n        {\n          \"name\": \"byteArrayTest (the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...))\",\n          \"type\": 7,\n          \"values\": [\n            [\n              0,\n              62,\n              34,\n              \"Removed for the sake of brevity...\"\n            ]\n          ]\n        },\n        {\n          \"name\": \"shortTest\",\n          \"type\": 2,\n          \"value\": 32767\n        }\n      ]\n    }\n  ]\n}\n```\n\n\u003c/details\u003e\n\n...or perhaps you need to stringify it into SNBT format...\n\n```ruby\ntag.stringify\n```\n\n```\n{Level:{{nested compound test:{{egg:{name:\"Eggburt\",value:0.5F},{ham:{name:\"Hampus\",value:0.75F},intTest:2147483647,byteTest:127B,stringTest:\"HELLO WORLD THIS IS A TEST STRING ÅÄÖ!\",listTest (long):[11L,12L,13L,14L,15L],doubleTest:0.4931287132182315,floatTest:0.4982314705848694F,longTest:9223372036854775807L,listTest (compound:[{{created-on:1264099775885L,name:\"Compound tag #0\"},{{created-on:1264099775885L,name:\"Compound tag #1\"}],byteArrayTest (the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...)):[B;0,62,...,74,6,48],shortTest:32767S}}\n```\n\n### Parsing Stringified NBT (SNBT)\n\nFor parsing SNBT, this library uses a proper lexer with a grammar file approach, taking advantage of the Racc gem, which\nis part of Ruby's standard library. This allows scanning over input and tokenizing it into logical pieces to parse,\nopposed to using monstrous and difficult-to-debug regular expressions.\n\nThere is only a single method call involved with parsing an arbitrary string of SNBT code: `NBT.parse_snbt`.\n\n```ruby\nsnbt_string = '{name1:123,name2:\"sometext1\",name3:{subname1:456,subname2:\"sometext2\"}}'\ncompound = NBT.parse_snbt(snbt_string)\ncompound.pretty_print\n```\n\n**Output:**\n```\nTAG_Compound(None): 3 children\n{\n    TAG_Int(\"name1\"): 123\n    TAG_String(\"name2\"): \"sometext1\"\n    TAG_Compound(\"name3\"): 2 children\n    {\n        TAG_Int(\"subname1\"): 456\n        TAG_String(\"subname2\"): \"sometext2\"\n    }\n}\n```\n## Documentation\n\nCode is fully documented using [YARD](https://yardoc.org/), which is supported by modern linters for inline documentation\nin your editor, and is always available [in full at RubyDoc.info](https://www.rubydoc.info/gems/craftbook-nbt).\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/ForeverZer0/craftbook-nbt. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/ForeverZer0/craftbook-nbt/blob/master/CODE_OF_CONDUCT.md).\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n## Code of Conduct\n\nEveryone interacting in the Craftbook::Nbt project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/ForeverZer0/craftbook-nbt/blob/master/CODE_OF_CONDUCT.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fforeverzer0%2Fcraftbook-nbt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fforeverzer0%2Fcraftbook-nbt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fforeverzer0%2Fcraftbook-nbt/lists"}