forked from rojo-rbx/rojo
Update rbx-dom (#1023)
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
# Rojo Changelog
|
||||
|
||||
## Unreleased Changes
|
||||
* Added support for Roblox's `Content` type. This replaces the old `Content` type with `ContentId` to reflect Roblox's change.
|
||||
If you were previously using the fully-qualified syntax for `Content` you will need to switch it to `ContentId`.
|
||||
* Added support for `Enum` attributes
|
||||
* Significantly improved performance of `.rbxm` parsing
|
||||
* Support for a `$schema` field in all special JSON files (`.project.json`, `.model.json`, and `.meta.json`) ([#974])
|
||||
* Projects may now manually link `Ref` properties together using `Attributes`. ([#843])
|
||||
This has two parts: using `id` or `$id` in JSON files or a `Rojo_Target` attribute, an Instance
|
||||
|
||||
178
Cargo.lock
generated
178
Cargo.lock
generated
@@ -17,6 +17,19 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.2"
|
||||
@@ -171,6 +184,10 @@ name = "cc"
|
||||
version = "1.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0ba8f7aaa012f30d5b2861462f6708eccd49c3c39863fe083a308035f63d723"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
@@ -492,7 +509,7 @@ checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.4.1",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
@@ -935,6 +952,15 @@ version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jod-thread"
|
||||
version = "0.1.2"
|
||||
@@ -962,9 +988,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "lazycell"
|
||||
@@ -986,7 +1012,7 @@ checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1001,6 +1027,16 @@ version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.21"
|
||||
@@ -1241,6 +1277,29 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"redox_syscall 0.5.10",
|
||||
"smallvec",
|
||||
"windows-targets 0.52.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.14"
|
||||
@@ -1310,6 +1369,12 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||
|
||||
[[package]]
|
||||
name = "plotters"
|
||||
version = "0.3.5"
|
||||
@@ -1498,10 +1563,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rbx_binary"
|
||||
version = "0.7.7"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b85057e8ff75a1ce99248200c4b3c7b481a3d52f921f1053ecd67921dcc7930"
|
||||
checksum = "9573fee5e073d7b303f475c285197fdc8179468de66ca60ee115a58fbac99296"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"log",
|
||||
"lz4",
|
||||
"profiling",
|
||||
@@ -1509,23 +1575,26 @@ dependencies = [
|
||||
"rbx_reflection",
|
||||
"rbx_reflection_database",
|
||||
"thiserror",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rbx_dom_weak"
|
||||
version = "2.9.0"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcd2a17d09e46af0805f8b311a926402172b97e8d9388745c9adf8f448901841"
|
||||
checksum = "04425cf6e9376e5486f4fb35906c120d1b1b45618a490318cf563fab1fa230a9"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"rbx_types",
|
||||
"serde",
|
||||
"ustr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rbx_reflection"
|
||||
version = "4.7.0"
|
||||
version = "5.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8118ac6021d700e8debe324af6b40ecfd2cef270a00247849dbdfeebb0802677"
|
||||
checksum = "1b6d0d62baa613556b058a5f94a53b01cf0ccde0ea327ce03056e335b982e77e"
|
||||
dependencies = [
|
||||
"rbx_types",
|
||||
"serde",
|
||||
@@ -1534,9 +1603,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rbx_reflection_database"
|
||||
version = "0.2.12+roblox-638"
|
||||
version = "1.0.0+roblox-666"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e29381d675420e841f8c02db5755cbb2545ed3e13f56c539546dc58702b512a"
|
||||
checksum = "a45b98a2794815736602087cf2fc9d85eb798e7c432d41307336014792768a46"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"rbx_reflection",
|
||||
@@ -1546,9 +1615,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rbx_types"
|
||||
version = "1.10.0"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e30f49b2a3bb667e4074ba73c2dfb8ca0873f610b448ccf318a240acfdec6c73"
|
||||
checksum = "78e4fdde46493def107e5f923d82e813dec9b3eef52c2f75fbad3a716023eda2"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"bitflags 1.3.2",
|
||||
@@ -1561,10 +1630,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rbx_xml"
|
||||
version = "0.13.5"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b14b3027bc9ccd82e2fc854c8bcd25ed58318e570c355bf2cf63df9cdbd5ba8"
|
||||
checksum = "bb623833c31cc43bbdaeb32f5e91db8ecd63fc46e438d0d268baf9e61539cf1c"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"base64 0.13.1",
|
||||
"log",
|
||||
"rbx_dom_weak",
|
||||
@@ -1582,6 +1652,15 @@ dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.4"
|
||||
@@ -1896,6 +1975,12 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "sct"
|
||||
version = "0.7.1"
|
||||
@@ -2393,6 +2478,19 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ustr"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18b19e258aa08450f93369cf56dd78063586adf19e92a75b338a800f799a0208"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"byteorder",
|
||||
"lazy_static",
|
||||
"parking_lot",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.7.0"
|
||||
@@ -2784,3 +2882,51 @@ name = "yansi"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.78",
|
||||
"quote 1.0.35",
|
||||
"syn 2.0.52",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
|
||||
dependencies = [
|
||||
"zstd-safe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-safe"
|
||||
version = "7.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
|
||||
dependencies = [
|
||||
"zstd-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-sys"
|
||||
version = "2.0.15+zstd.1.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
10
Cargo.toml
10
Cargo.toml
@@ -51,11 +51,11 @@ memofs = { version = "0.3.0", path = "crates/memofs" }
|
||||
# rbx_reflection_database = { path = "../rbx-dom/rbx_reflection_database" }
|
||||
# rbx_xml = { path = "../rbx-dom/rbx_xml" }
|
||||
|
||||
rbx_binary = "0.7.7"
|
||||
rbx_dom_weak = "2.9.0"
|
||||
rbx_reflection = "4.7.0"
|
||||
rbx_reflection_database = "0.2.12"
|
||||
rbx_xml = "0.13.5"
|
||||
rbx_binary = "1.0.0"
|
||||
rbx_dom_weak = "3.0.0"
|
||||
rbx_reflection = "5.0.0"
|
||||
rbx_reflection_database = "1.0.0"
|
||||
rbx_xml = "1.0.0"
|
||||
|
||||
anyhow = "1.0.80"
|
||||
backtrace = "0.3.69"
|
||||
|
||||
@@ -188,6 +188,38 @@ types = {
|
||||
},
|
||||
|
||||
Content = {
|
||||
fromPod = function(pod): Content
|
||||
if type(pod) == "string" then
|
||||
if pod == "None" then
|
||||
return Content.none
|
||||
else
|
||||
error(`unexpected Content value '{pod}'`)
|
||||
end
|
||||
else
|
||||
local ty, value = next(pod)
|
||||
if ty == "Uri" then
|
||||
return Content.fromUri(value)
|
||||
elseif ty == "Object" then
|
||||
error("Object deserializing is not currently implemented")
|
||||
else
|
||||
error(`Unknown Content type '{ty}' (could not deserialize)`)
|
||||
end
|
||||
end
|
||||
end,
|
||||
toPod = function(roblox: Content)
|
||||
if roblox.SourceType == Enum.ContentSourceType.None then
|
||||
return "None"
|
||||
elseif roblox.SourceType == Enum.ContentSourceType.Uri then
|
||||
return { Uri = roblox.Uri }
|
||||
elseif roblox.SourceType == Enum.ContentSourceType.Object then
|
||||
error("Object serializing is not currently implemented")
|
||||
else
|
||||
error(`Unknown Content type '{roblox.SourceType} (could not serialize)`)
|
||||
end
|
||||
end,
|
||||
},
|
||||
|
||||
ContentId = {
|
||||
fromPod = identity,
|
||||
toPod = identity,
|
||||
},
|
||||
@@ -205,6 +237,19 @@ types = {
|
||||
end,
|
||||
},
|
||||
|
||||
EnumItem = {
|
||||
fromPod = function(pod)
|
||||
return Enum[pod.type]:FromValue(pod.value)
|
||||
end,
|
||||
|
||||
toPod = function(roblox)
|
||||
return {
|
||||
type = tostring(roblox.EnumType),
|
||||
value = roblox.Value,
|
||||
}
|
||||
end,
|
||||
},
|
||||
|
||||
Faces = {
|
||||
fromPod = function(pod)
|
||||
local faces = {}
|
||||
@@ -300,7 +345,12 @@ types = {
|
||||
local keypoints = {}
|
||||
|
||||
for index, keypoint in ipairs(pod.keypoints) do
|
||||
keypoints[index] = NumberSequenceKeypoint.new(keypoint.time, keypoint.value, keypoint.envelope)
|
||||
-- TODO: Add a test for NaN or Infinity values and envelopes
|
||||
-- Right now it isn't possible because it'd fail the roundtrip.
|
||||
-- It's more important that it works right now, though.
|
||||
local value = keypoint.value or 0
|
||||
local envelope = keypoint.envelope or 0
|
||||
keypoints[index] = NumberSequenceKeypoint.new(keypoint.time, value, envelope)
|
||||
end
|
||||
|
||||
return NumberSequence.new(keypoints)
|
||||
|
||||
@@ -5,6 +5,7 @@ Error.Kind = {
|
||||
UnknownProperty = "UnknownProperty",
|
||||
PropertyNotReadable = "PropertyNotReadable",
|
||||
PropertyNotWritable = "PropertyNotWritable",
|
||||
CannotParseBinaryString = "CannotParseBinaryString",
|
||||
Roblox = "Roblox",
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,12 @@
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"TestEnumItem": {
|
||||
"EnumItem": {
|
||||
"type": "Material",
|
||||
"value": 256
|
||||
}
|
||||
},
|
||||
"TestNumber": {
|
||||
"Float64": 1337.0
|
||||
},
|
||||
@@ -170,9 +176,23 @@
|
||||
},
|
||||
"ty": "ColorSequence"
|
||||
},
|
||||
"Content": {
|
||||
"ContentId": {
|
||||
"value": {
|
||||
"Content": "rbxassetid://12345"
|
||||
"ContentId": "rbxassetid://12345"
|
||||
},
|
||||
"ty": "ContentId"
|
||||
},
|
||||
"Content_None": {
|
||||
"value": {
|
||||
"Content": "None"
|
||||
},
|
||||
"ty": "Content"
|
||||
},
|
||||
"Content_Uri": {
|
||||
"value": {
|
||||
"Content": {
|
||||
"Uri": "rbxasset://abc/123.rojo"
|
||||
}
|
||||
},
|
||||
"ty": "Content"
|
||||
},
|
||||
@@ -182,6 +202,15 @@
|
||||
},
|
||||
"ty": "Enum"
|
||||
},
|
||||
"EnumItem": {
|
||||
"value": {
|
||||
"EnumItem": {
|
||||
"type": "Material",
|
||||
"value": 256
|
||||
}
|
||||
},
|
||||
"ty": "EnumItem"
|
||||
},
|
||||
"Faces": {
|
||||
"value": {
|
||||
"Faces": [
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
local CollectionService = game:GetService("CollectionService")
|
||||
local ScriptEditorService = game:GetService("ScriptEditorService")
|
||||
|
||||
local Error = require(script.Parent.Error)
|
||||
|
||||
--- A list of `Enum.Material` values that are used for Terrain.MaterialColors
|
||||
local TERRAIN_MATERIAL_COLORS = {
|
||||
Enum.Material.Grass,
|
||||
@@ -51,6 +53,10 @@ return {
|
||||
return true, instance:GetAttributes()
|
||||
end,
|
||||
write = function(instance, _, value)
|
||||
if typeof(value) ~= "table" then
|
||||
return false, Error.new(Error.Kind.CannotParseBinaryString)
|
||||
end
|
||||
|
||||
local existing = instance:GetAttributes()
|
||||
local didAllWritesSucceed = true
|
||||
|
||||
@@ -160,9 +166,14 @@ return {
|
||||
return true, colors
|
||||
end,
|
||||
write = function(instance: Terrain, _, value: { [Enum.Material]: Color3 })
|
||||
if typeof(value) ~= "table" then
|
||||
return false, Error.new(Error.Kind.CannotParseBinaryString)
|
||||
end
|
||||
|
||||
for material, color in value do
|
||||
instance:SetMaterialColor(material, color)
|
||||
end
|
||||
|
||||
return true
|
||||
end,
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ use clap::Parser;
|
||||
use fs_err::File;
|
||||
use memofs::Vfs;
|
||||
use rayon::prelude::*;
|
||||
use rbx_dom_weak::types::Ref;
|
||||
use rbx_dom_weak::{types::Ref, Ustr};
|
||||
use serde::Serialize;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
@@ -26,7 +26,7 @@ const PATH_STRIP_FAILED_ERR: &str = "Failed to create relative paths for project
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct SourcemapNode<'a> {
|
||||
name: &'a str,
|
||||
class_name: &'a str,
|
||||
class_name: Ustr,
|
||||
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
file_paths: Vec<PathBuf>,
|
||||
@@ -113,7 +113,7 @@ fn filter_nothing(_instance: &InstanceWithMeta) -> bool {
|
||||
|
||||
fn filter_non_scripts(instance: &InstanceWithMeta) -> bool {
|
||||
matches!(
|
||||
instance.class_name(),
|
||||
instance.class_name().as_str(),
|
||||
"Script" | "LocalScript" | "ModuleScript"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use std::{
|
||||
};
|
||||
|
||||
use memofs::Vfs;
|
||||
use rbx_dom_weak::{Ustr, UstrMap};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -310,7 +311,7 @@ pub struct ProjectNode {
|
||||
/// `$className` CANNOT be set if `$path` is set and the instance described
|
||||
/// by that path has a ClassName other than Folder.
|
||||
#[serde(rename = "$className", skip_serializing_if = "Option::is_none")]
|
||||
pub class_name: Option<String>,
|
||||
pub class_name: Option<Ustr>,
|
||||
|
||||
/// If set, defines an ID for the described Instance that can be used
|
||||
/// to refer to it for the purpose of referent properties.
|
||||
@@ -329,7 +330,7 @@ pub struct ProjectNode {
|
||||
default,
|
||||
skip_serializing_if = "HashMap::is_empty"
|
||||
)]
|
||||
pub properties: HashMap<String, UnresolvedValue>,
|
||||
pub properties: UstrMap<UnresolvedValue>,
|
||||
|
||||
#[serde(
|
||||
rename = "$attributes",
|
||||
|
||||
@@ -2,8 +2,8 @@ use std::borrow::Borrow;
|
||||
|
||||
use anyhow::{bail, format_err};
|
||||
use rbx_dom_weak::types::{
|
||||
Attributes, CFrame, Color3, Content, Enum, Font, MaterialColors, Matrix3, Tags, Variant,
|
||||
VariantType, Vector2, Vector3,
|
||||
Attributes, CFrame, Color3, Content, ContentId, Enum, Font, MaterialColors, Matrix3, Tags,
|
||||
Variant, VariantType, Vector2, Vector3,
|
||||
};
|
||||
use rbx_reflection::{DataType, PropertyDescriptor};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -116,6 +116,9 @@ impl AmbiguousValue {
|
||||
(VariantType::Content, AmbiguousValue::String(value)) => {
|
||||
Ok(Content::from(value).into())
|
||||
}
|
||||
(VariantType::ContentId, AmbiguousValue::String(value)) => {
|
||||
Ok(ContentId::from(value).into())
|
||||
}
|
||||
|
||||
(VariantType::Vector2, AmbiguousValue::Array2(value)) => {
|
||||
Ok(Vector2::new(value[0] as f32, value[1] as f32).into())
|
||||
@@ -275,10 +278,20 @@ mod test {
|
||||
Variant::String("Hello!".into()),
|
||||
);
|
||||
|
||||
// String literals can also turn into Content
|
||||
// String literals can also turn into ContentId
|
||||
assert_eq!(
|
||||
resolve("Sky", "MoonTextureId", "\"rbxassetid://12345\""),
|
||||
Variant::Content("rbxassetid://12345".into()),
|
||||
Variant::ContentId("rbxassetid://12345".into()),
|
||||
);
|
||||
|
||||
// String literals can turn into Content!
|
||||
assert_eq!(
|
||||
resolve(
|
||||
"MeshPart",
|
||||
"MeshContent",
|
||||
"\"rbxasset://totally-a-real-uri.tiff\""
|
||||
),
|
||||
Variant::Content("rbxasset://totally-a-real-uri.tiff".into())
|
||||
);
|
||||
|
||||
// What about BinaryString values? For forward-compatibility reasons, we
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
//! Defines the structure of an instance snapshot.
|
||||
|
||||
use std::{borrow::Cow, collections::HashMap};
|
||||
use std::borrow::Cow;
|
||||
|
||||
use rbx_dom_weak::{
|
||||
types::{Ref, Variant},
|
||||
Instance, WeakDom,
|
||||
ustr, AHashMap, HashMapExt as _, Instance, Ustr, UstrMap, WeakDom,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -27,10 +27,10 @@ pub struct InstanceSnapshot {
|
||||
pub name: Cow<'static, str>,
|
||||
|
||||
/// Corresponds to the ClassName property of the instance.
|
||||
pub class_name: Cow<'static, str>,
|
||||
pub class_name: Ustr,
|
||||
|
||||
/// All other properties of the instance, weakly-typed.
|
||||
pub properties: HashMap<String, Variant>,
|
||||
pub properties: UstrMap<Variant>,
|
||||
|
||||
/// The children of the instance represented as more snapshots.
|
||||
///
|
||||
@@ -44,8 +44,8 @@ impl InstanceSnapshot {
|
||||
snapshot_id: Ref::none(),
|
||||
metadata: InstanceMetadata::default(),
|
||||
name: Cow::Borrowed("DEFAULT"),
|
||||
class_name: Cow::Borrowed("DEFAULT"),
|
||||
properties: HashMap::new(),
|
||||
class_name: ustr("DEFAULT"),
|
||||
properties: UstrMap::new(),
|
||||
children: Vec::new(),
|
||||
}
|
||||
}
|
||||
@@ -57,23 +57,23 @@ impl InstanceSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn class_name(self, class_name: impl Into<String>) -> Self {
|
||||
pub fn class_name<S: Into<Ustr>>(self, class_name: S) -> Self {
|
||||
Self {
|
||||
class_name: Cow::Owned(class_name.into()),
|
||||
class_name: class_name.into(),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn property<K, V>(mut self, key: K, value: V) -> Self
|
||||
where
|
||||
K: Into<String>,
|
||||
K: Into<Ustr>,
|
||||
V: Into<Variant>,
|
||||
{
|
||||
self.properties.insert(key.into(), value.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn properties(self, properties: impl Into<HashMap<String, Variant>>) -> Self {
|
||||
pub fn properties(self, properties: impl Into<UstrMap<Variant>>) -> Self {
|
||||
Self {
|
||||
properties: properties.into(),
|
||||
..self
|
||||
@@ -107,7 +107,7 @@ impl InstanceSnapshot {
|
||||
Self::from_raw_tree(&mut raw_tree, id)
|
||||
}
|
||||
|
||||
fn from_raw_tree(raw_tree: &mut HashMap<Ref, Instance>, id: Ref) -> Self {
|
||||
fn from_raw_tree(raw_tree: &mut AHashMap<Ref, Instance>, id: Ref) -> Self {
|
||||
let instance = raw_tree
|
||||
.remove(&id)
|
||||
.expect("instance did not exist in tree");
|
||||
@@ -122,7 +122,7 @@ impl InstanceSnapshot {
|
||||
snapshot_id: id,
|
||||
metadata: InstanceMetadata::default(),
|
||||
name: Cow::Owned(instance.name),
|
||||
class_name: Cow::Owned(instance.class),
|
||||
class_name: instance.class,
|
||||
properties: instance.properties,
|
||||
children,
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
//! Defines the data structures used for describing instance patches.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use rbx_dom_weak::types::{Ref, Variant};
|
||||
use rbx_dom_weak::{
|
||||
types::{Ref, Variant},
|
||||
HashMapExt as _, Ustr, UstrMap,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{InstanceMetadata, InstanceSnapshot};
|
||||
@@ -41,11 +42,11 @@ pub struct PatchAdd {
|
||||
pub struct PatchUpdate {
|
||||
pub id: Ref,
|
||||
pub changed_name: Option<String>,
|
||||
pub changed_class_name: Option<String>,
|
||||
pub changed_class_name: Option<Ustr>,
|
||||
|
||||
/// Contains all changed properties. If a property is assigned to `None`,
|
||||
/// then that property has been removed.
|
||||
pub changed_properties: HashMap<String, Option<Variant>>,
|
||||
pub changed_properties: UstrMap<Option<Variant>>,
|
||||
|
||||
/// Changed Rojo-specific metadata, if any of it changed.
|
||||
pub changed_metadata: Option<InstanceMetadata>,
|
||||
@@ -88,8 +89,8 @@ pub struct AppliedPatchUpdate {
|
||||
|
||||
// TODO: Store previous values in order to detect application conflicts
|
||||
pub changed_name: Option<String>,
|
||||
pub changed_class_name: Option<String>,
|
||||
pub changed_properties: HashMap<String, Option<Variant>>,
|
||||
pub changed_class_name: Option<Ustr>,
|
||||
pub changed_properties: UstrMap<Option<Variant>>,
|
||||
pub changed_metadata: Option<InstanceMetadata>,
|
||||
}
|
||||
|
||||
@@ -99,7 +100,7 @@ impl AppliedPatchUpdate {
|
||||
id,
|
||||
changed_name: None,
|
||||
changed_class_name: None,
|
||||
changed_properties: HashMap::new(),
|
||||
changed_properties: UstrMap::new(),
|
||||
changed_metadata: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,10 @@ use std::{
|
||||
mem::take,
|
||||
};
|
||||
|
||||
use rbx_dom_weak::types::{Ref, Variant};
|
||||
use rbx_dom_weak::{
|
||||
types::{Ref, Variant},
|
||||
ustr, Ustr,
|
||||
};
|
||||
|
||||
use super::{
|
||||
patch::{AppliedPatchSet, AppliedPatchUpdate, PatchSet, PatchUpdate},
|
||||
@@ -76,7 +79,7 @@ struct PatchApplyContext {
|
||||
/// Tracks all ref properties that were specified using attributes. This has
|
||||
/// to be handled after everything else is done just like normal referent
|
||||
/// properties.
|
||||
attribute_refs_to_rewrite: MultiMap<Ref, (String, String)>,
|
||||
attribute_refs_to_rewrite: MultiMap<Ref, (Ustr, String)>,
|
||||
|
||||
/// The current applied patch result, describing changes made to the tree.
|
||||
applied_patch_set: AppliedPatchSet,
|
||||
@@ -193,7 +196,7 @@ fn apply_update_child(context: &mut PatchApplyContext, tree: &mut RojoTree, patc
|
||||
}
|
||||
|
||||
if let Some(class_name) = patch.changed_class_name {
|
||||
*instance.class_name_mut() = class_name.clone();
|
||||
instance.set_class_name(class_name);
|
||||
applied_patch.changed_class_name = Some(class_name);
|
||||
}
|
||||
|
||||
@@ -219,10 +222,10 @@ fn apply_update_child(context: &mut PatchApplyContext, tree: &mut RojoTree, patc
|
||||
|
||||
instance
|
||||
.properties_mut()
|
||||
.insert(key.clone(), Variant::Ref(new_referent));
|
||||
.insert(key, Variant::Ref(new_referent));
|
||||
}
|
||||
Some(ref value) => {
|
||||
instance.properties_mut().insert(key.clone(), value.clone());
|
||||
instance.properties_mut().insert(key, value.clone());
|
||||
}
|
||||
None => {
|
||||
instance.properties_mut().remove(&key);
|
||||
@@ -247,7 +250,7 @@ fn defer_ref_properties(tree: &mut RojoTree, id: Ref, context: &mut PatchApplyCo
|
||||
let instance = tree
|
||||
.get_instance(id)
|
||||
.expect("Instances should exist when calculating deferred refs");
|
||||
let attributes = match instance.properties().get("Attributes") {
|
||||
let attributes = match instance.properties().get(&ustr("Attributes")) {
|
||||
Some(Variant::Attributes(attrs)) => attrs,
|
||||
_ => return,
|
||||
};
|
||||
@@ -275,12 +278,12 @@ fn defer_ref_properties(tree: &mut RojoTree, id: Ref, context: &mut PatchApplyCo
|
||||
if let Variant::String(prop_value) = attr_value {
|
||||
context
|
||||
.attribute_refs_to_rewrite
|
||||
.insert(id, (prop_name.to_owned(), prop_value.clone()));
|
||||
.insert(id, (ustr(prop_name), prop_value.clone()));
|
||||
} else if let Variant::BinaryString(prop_value) = attr_value {
|
||||
if let Ok(str) = std::str::from_utf8(prop_value.as_ref()) {
|
||||
context
|
||||
.attribute_refs_to_rewrite
|
||||
.insert(id, (prop_name.to_owned(), str.to_string()));
|
||||
.insert(id, (ustr(prop_name), str.to_string()));
|
||||
} else {
|
||||
log::error!("IDs specified by referent property attributes must be valid UTF-8 strings.")
|
||||
}
|
||||
@@ -304,7 +307,7 @@ mod test {
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use rbx_dom_weak::types::Variant;
|
||||
use rbx_dom_weak::{types::Variant, UstrMap};
|
||||
|
||||
use super::super::PatchAdd;
|
||||
|
||||
@@ -320,8 +323,8 @@ mod test {
|
||||
snapshot_id: Ref::none(),
|
||||
metadata: Default::default(),
|
||||
name: Cow::Borrowed("Foo"),
|
||||
class_name: Cow::Borrowed("Bar"),
|
||||
properties: [("Baz".to_owned(), Variant::Int32(5))].into(),
|
||||
class_name: ustr("Bar"),
|
||||
properties: UstrMap::from_iter([(ustr("Baz"), Variant::Int32(5))]),
|
||||
children: Vec::new(),
|
||||
};
|
||||
|
||||
@@ -340,7 +343,7 @@ mod test {
|
||||
let child_instance = tree.get_instance(child_id).unwrap();
|
||||
|
||||
assert_eq!(child_instance.name(), &snapshot.name);
|
||||
assert_eq!(child_instance.class_name(), &snapshot.class_name);
|
||||
assert_eq!(child_instance.class_name(), snapshot.class_name);
|
||||
assert_eq!(child_instance.properties(), &snapshot.properties);
|
||||
assert!(child_instance.children().is_empty());
|
||||
}
|
||||
@@ -363,16 +366,15 @@ mod test {
|
||||
let patch = PatchUpdate {
|
||||
id: root_id,
|
||||
changed_name: Some("Foo".to_owned()),
|
||||
changed_class_name: Some("NewClassName".to_owned()),
|
||||
changed_properties: [
|
||||
changed_class_name: Some(ustr("NewClassName")),
|
||||
changed_properties: UstrMap::from_iter([
|
||||
// The value of Foo has changed
|
||||
("Foo".to_owned(), Some(Variant::Int32(8))),
|
||||
(ustr("Foo"), Some(Variant::Int32(8))),
|
||||
// Bar has been deleted
|
||||
("Bar".to_owned(), None),
|
||||
(ustr("Bar"), None),
|
||||
// Baz has been added
|
||||
("Baz".to_owned(), Some(Variant::Int32(10))),
|
||||
]
|
||||
.into(),
|
||||
(ustr("Baz"), Some(Variant::Int32(10))),
|
||||
]),
|
||||
changed_metadata: None,
|
||||
};
|
||||
|
||||
@@ -383,12 +385,11 @@ mod test {
|
||||
|
||||
apply_patch_set(&mut tree, patch_set);
|
||||
|
||||
let expected_properties = [
|
||||
("Foo".to_owned(), Variant::Int32(8)),
|
||||
("Baz".to_owned(), Variant::Int32(10)),
|
||||
("Unchanged".to_owned(), Variant::Int32(-5)),
|
||||
]
|
||||
.into();
|
||||
let expected_properties = UstrMap::from_iter([
|
||||
(ustr("Foo"), Variant::Int32(8)),
|
||||
(ustr("Baz"), Variant::Int32(10)),
|
||||
(ustr("Unchanged"), Variant::Int32(-5)),
|
||||
]);
|
||||
|
||||
let root_instance = tree.get_instance(root_id).unwrap();
|
||||
assert_eq!(root_instance.name(), "Foo");
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
//! Defines the algorithm for computing a roughly-minimal patch set given an
|
||||
//! existing instance tree and an instance snapshot.
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
mem::take,
|
||||
};
|
||||
use std::{collections::HashMap, mem::take};
|
||||
|
||||
use rbx_dom_weak::types::{Ref, Variant};
|
||||
use rbx_dom_weak::{
|
||||
types::{Ref, Variant},
|
||||
ustr, HashMapExt as _, UstrMap, UstrSet,
|
||||
};
|
||||
|
||||
use crate::{RojoRef, REF_POINTER_ATTRIBUTE_PREFIX};
|
||||
|
||||
@@ -99,8 +99,8 @@ fn compute_property_patches(
|
||||
patch_set: &mut PatchSet,
|
||||
tree: &RojoTree,
|
||||
) {
|
||||
let mut visited_properties = HashSet::new();
|
||||
let mut changed_properties = HashMap::new();
|
||||
let mut visited_properties = UstrSet::default();
|
||||
let mut changed_properties = UstrMap::new();
|
||||
|
||||
let attribute_ref_properties = compute_ref_properties(snapshot, tree);
|
||||
|
||||
@@ -113,7 +113,7 @@ fn compute_property_patches(
|
||||
let changed_class_name = if snapshot.class_name == instance.class_name() {
|
||||
None
|
||||
} else {
|
||||
Some(take(&mut snapshot.class_name).into_owned())
|
||||
Some(take(&mut snapshot.class_name))
|
||||
};
|
||||
|
||||
let changed_metadata = if &snapshot.metadata == instance.metadata() {
|
||||
@@ -123,7 +123,7 @@ fn compute_property_patches(
|
||||
};
|
||||
|
||||
for (name, snapshot_value) in take(&mut snapshot.properties) {
|
||||
visited_properties.insert(name.clone());
|
||||
visited_properties.insert(name);
|
||||
|
||||
match instance.properties().get(&name) {
|
||||
Some(instance_value) => {
|
||||
@@ -138,11 +138,11 @@ fn compute_property_patches(
|
||||
}
|
||||
|
||||
for name in instance.properties().keys() {
|
||||
if visited_properties.contains(name.as_str()) {
|
||||
if visited_properties.contains(name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
changed_properties.insert(name.clone(), None);
|
||||
changed_properties.insert(*name, None);
|
||||
}
|
||||
|
||||
for (name, ref_value) in attribute_ref_properties {
|
||||
@@ -250,9 +250,9 @@ fn compute_children_patches(
|
||||
fn compute_ref_properties(
|
||||
snapshot: &InstanceSnapshot,
|
||||
tree: &RojoTree,
|
||||
) -> HashMap<String, Option<Variant>> {
|
||||
let mut map = HashMap::new();
|
||||
let attributes = match snapshot.properties.get("Attributes") {
|
||||
) -> UstrMap<Option<Variant>> {
|
||||
let mut map = UstrMap::new();
|
||||
let attributes = match snapshot.properties.get(&ustr("Attributes")) {
|
||||
Some(Variant::Attributes(attrs)) => attrs,
|
||||
_ => return map,
|
||||
};
|
||||
@@ -284,9 +284,9 @@ fn compute_ref_properties(
|
||||
}
|
||||
};
|
||||
if let Some(target_id) = tree.get_specified_id(&rojo_ref) {
|
||||
map.insert(prop_name.to_string(), Some(Variant::Ref(target_id)));
|
||||
map.insert(ustr(prop_name), Some(Variant::Ref(target_id)));
|
||||
} else {
|
||||
map.insert(prop_name.to_string(), None);
|
||||
map.insert(ustr(prop_name), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,11 +314,11 @@ mod test {
|
||||
let snapshot_id = Ref::new();
|
||||
let snapshot = InstanceSnapshot {
|
||||
snapshot_id,
|
||||
properties: [("Self".to_owned(), Variant::Ref(snapshot_id))].into(),
|
||||
properties: UstrMap::from_iter([(ustr("Self"), Variant::Ref(snapshot_id))]),
|
||||
|
||||
metadata: Default::default(),
|
||||
name: Cow::Borrowed("foo"),
|
||||
class_name: Cow::Borrowed("foo"),
|
||||
class_name: ustr("foo"),
|
||||
children: Vec::new(),
|
||||
};
|
||||
|
||||
@@ -329,7 +329,10 @@ mod test {
|
||||
id: root_id,
|
||||
changed_name: None,
|
||||
changed_class_name: None,
|
||||
changed_properties: [("Self".to_owned(), Some(Variant::Ref(root_id)))].into(),
|
||||
changed_properties: UstrMap::from_iter([(
|
||||
ustr("Self"),
|
||||
Some(Variant::Ref(root_id)),
|
||||
)]),
|
||||
changed_metadata: None,
|
||||
}],
|
||||
added_instances: Vec::new(),
|
||||
@@ -353,19 +356,19 @@ mod test {
|
||||
let snapshot = InstanceSnapshot {
|
||||
snapshot_id,
|
||||
children: vec![InstanceSnapshot {
|
||||
properties: [("Self".to_owned(), Variant::Ref(snapshot_id))].into(),
|
||||
properties: UstrMap::from_iter([(ustr("Self"), Variant::Ref(snapshot_id))]),
|
||||
|
||||
snapshot_id: Ref::none(),
|
||||
metadata: Default::default(),
|
||||
name: Cow::Borrowed("child"),
|
||||
class_name: Cow::Borrowed("child"),
|
||||
class_name: ustr("child"),
|
||||
children: Vec::new(),
|
||||
}],
|
||||
|
||||
metadata: Default::default(),
|
||||
properties: HashMap::new(),
|
||||
properties: UstrMap::new(),
|
||||
name: Cow::Borrowed("foo"),
|
||||
class_name: Cow::Borrowed("foo"),
|
||||
class_name: ustr("foo"),
|
||||
};
|
||||
|
||||
let patch_set = compute_patch_set(Some(snapshot), &tree, root_id);
|
||||
@@ -376,9 +379,9 @@ mod test {
|
||||
instance: InstanceSnapshot {
|
||||
snapshot_id: Ref::none(),
|
||||
metadata: Default::default(),
|
||||
properties: [("Self".to_owned(), Variant::Ref(root_id))].into(),
|
||||
properties: UstrMap::from_iter([(ustr("Self"), Variant::Ref(root_id))]),
|
||||
name: Cow::Borrowed("child"),
|
||||
class_name: Cow::Borrowed("child"),
|
||||
class_name: ustr("child"),
|
||||
children: Vec::new(),
|
||||
},
|
||||
}],
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use insta::assert_yaml_snapshot;
|
||||
|
||||
use rbx_dom_weak::{ustr, UstrMap};
|
||||
use rojo_insta_ext::RedactionMap;
|
||||
|
||||
use crate::{
|
||||
@@ -18,7 +19,7 @@ fn set_name_and_class_name() {
|
||||
updated_instances: vec![PatchUpdate {
|
||||
id: tree.get_root_id(),
|
||||
changed_name: Some("Hello, world!".to_owned()),
|
||||
changed_class_name: Some("Folder".to_owned()),
|
||||
changed_class_name: Some(ustr("Folder")),
|
||||
changed_properties: Default::default(),
|
||||
changed_metadata: None,
|
||||
}],
|
||||
@@ -46,7 +47,7 @@ fn add_property() {
|
||||
id: tree.get_root_id(),
|
||||
changed_name: None,
|
||||
changed_class_name: None,
|
||||
changed_properties: [("Foo".to_owned(), Some("Value of Foo".into()))].into(),
|
||||
changed_properties: UstrMap::from_iter([(ustr("Foo"), Some("Value of Foo".into()))]),
|
||||
changed_metadata: None,
|
||||
}],
|
||||
..Default::default()
|
||||
@@ -74,7 +75,7 @@ fn remove_property() {
|
||||
|
||||
root_instance
|
||||
.properties_mut()
|
||||
.insert("Foo".to_owned(), "Should be removed".into());
|
||||
.insert(ustr("Foo"), "Should be removed".into());
|
||||
}
|
||||
|
||||
let tree_view = view_tree(&tree, &mut redactions);
|
||||
@@ -85,7 +86,7 @@ fn remove_property() {
|
||||
id: tree.get_root_id(),
|
||||
changed_name: None,
|
||||
changed_class_name: None,
|
||||
changed_properties: [("Foo".to_owned(), None)].into(),
|
||||
changed_properties: UstrMap::from_iter([(ustr("Foo"), None)]),
|
||||
changed_metadata: None,
|
||||
}],
|
||||
..Default::default()
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::borrow::Cow;
|
||||
|
||||
use insta::assert_yaml_snapshot;
|
||||
|
||||
use rbx_dom_weak::types::Ref;
|
||||
use rbx_dom_weak::{types::Ref, ustr, UstrMap};
|
||||
use rojo_insta_ext::RedactionMap;
|
||||
|
||||
use crate::snapshot::{compute_patch_set, InstanceSnapshot, RojoTree};
|
||||
@@ -18,7 +18,7 @@ fn set_name_and_class_name() {
|
||||
snapshot_id: Ref::none(),
|
||||
metadata: Default::default(),
|
||||
name: Cow::Borrowed("Some Folder"),
|
||||
class_name: Cow::Borrowed("Folder"),
|
||||
class_name: ustr("Folder"),
|
||||
properties: Default::default(),
|
||||
children: Vec::new(),
|
||||
};
|
||||
@@ -40,8 +40,8 @@ fn set_property() {
|
||||
snapshot_id: Ref::none(),
|
||||
metadata: Default::default(),
|
||||
name: Cow::Borrowed("ROOT"),
|
||||
class_name: Cow::Borrowed("ROOT"),
|
||||
properties: [("PropertyName".into(), "Hello, world!".into())].into(),
|
||||
class_name: ustr("ROOT"),
|
||||
properties: UstrMap::from_iter([(ustr("PropertyName"), "Hello, world!".into())]),
|
||||
children: Vec::new(),
|
||||
};
|
||||
|
||||
@@ -61,17 +61,16 @@ fn remove_property() {
|
||||
{
|
||||
let root_id = tree.get_root_id();
|
||||
let mut root_instance = tree.get_instance_mut(root_id).unwrap();
|
||||
root_instance.properties_mut().insert(
|
||||
"Foo".to_owned(),
|
||||
"This should be removed by the patch.".into(),
|
||||
);
|
||||
root_instance
|
||||
.properties_mut()
|
||||
.insert(ustr("Foo"), "This should be removed by the patch.".into());
|
||||
}
|
||||
|
||||
let snapshot = InstanceSnapshot {
|
||||
snapshot_id: Ref::none(),
|
||||
metadata: Default::default(),
|
||||
name: Cow::Borrowed("ROOT"),
|
||||
class_name: Cow::Borrowed("ROOT"),
|
||||
class_name: ustr("ROOT"),
|
||||
properties: Default::default(),
|
||||
children: Vec::new(),
|
||||
};
|
||||
@@ -93,13 +92,13 @@ fn add_child() {
|
||||
snapshot_id: Ref::none(),
|
||||
metadata: Default::default(),
|
||||
name: Cow::Borrowed("ROOT"),
|
||||
class_name: Cow::Borrowed("ROOT"),
|
||||
class_name: ustr("ROOT"),
|
||||
properties: Default::default(),
|
||||
children: vec![InstanceSnapshot {
|
||||
snapshot_id: Ref::none(),
|
||||
metadata: Default::default(),
|
||||
name: Cow::Borrowed("New"),
|
||||
class_name: Cow::Borrowed("Folder"),
|
||||
class_name: ustr("Folder"),
|
||||
properties: Default::default(),
|
||||
children: Vec::new(),
|
||||
}],
|
||||
@@ -132,7 +131,7 @@ fn remove_child() {
|
||||
snapshot_id: Ref::none(),
|
||||
metadata: Default::default(),
|
||||
name: Cow::Borrowed("ROOT"),
|
||||
class_name: Cow::Borrowed("ROOT"),
|
||||
class_name: ustr("ROOT"),
|
||||
properties: Default::default(),
|
||||
children: Vec::new(),
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::{
|
||||
|
||||
use rbx_dom_weak::{
|
||||
types::{Ref, Variant},
|
||||
Instance, InstanceBuilder, WeakDom,
|
||||
ustr, Instance, InstanceBuilder, Ustr, UstrMap, WeakDom,
|
||||
};
|
||||
|
||||
use crate::{multimap::MultiMap, RojoRef};
|
||||
@@ -95,7 +95,7 @@ impl RojoTree {
|
||||
|
||||
pub fn insert_instance(&mut self, parent_ref: Ref, snapshot: InstanceSnapshot) -> Ref {
|
||||
let builder = InstanceBuilder::empty()
|
||||
.with_class(snapshot.class_name.into_owned())
|
||||
.with_class(snapshot.class_name)
|
||||
.with_name(snapshot.name.into_owned())
|
||||
.with_properties(snapshot.properties);
|
||||
|
||||
@@ -283,11 +283,11 @@ impl<'a> InstanceWithMeta<'a> {
|
||||
&self.instance.name
|
||||
}
|
||||
|
||||
pub fn class_name(&self) -> &'a str {
|
||||
&self.instance.class
|
||||
pub fn class_name(&self) -> Ustr {
|
||||
self.instance.class
|
||||
}
|
||||
|
||||
pub fn properties(&self) -> &'a HashMap<String, Variant> {
|
||||
pub fn properties(&self) -> &'a UstrMap<Variant> {
|
||||
&self.instance.properties
|
||||
}
|
||||
|
||||
@@ -328,15 +328,15 @@ impl InstanceWithMetaMut<'_> {
|
||||
&self.instance.class
|
||||
}
|
||||
|
||||
pub fn class_name_mut(&mut self) -> &mut String {
|
||||
&mut self.instance.class
|
||||
pub fn set_class_name<'a, S: Into<&'a str>>(&mut self, new_class: S) {
|
||||
self.instance.class = ustr(new_class.into());
|
||||
}
|
||||
|
||||
pub fn properties(&self) -> &HashMap<String, Variant> {
|
||||
pub fn properties(&self) -> &UstrMap<Variant> {
|
||||
&self.instance.properties
|
||||
}
|
||||
|
||||
pub fn properties_mut(&mut self) -> &mut HashMap<String, Variant> {
|
||||
pub fn properties_mut(&mut self) -> &mut UstrMap<Variant> {
|
||||
&mut self.instance.properties
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::{collections::BTreeMap, path::Path};
|
||||
|
||||
use anyhow::Context;
|
||||
use memofs::{IoResultExt, Vfs};
|
||||
use rbx_dom_weak::ustr;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::snapshot::{InstanceContext, InstanceMetadata, InstanceSnapshot};
|
||||
@@ -30,7 +31,7 @@ pub fn snapshot_csv(
|
||||
let mut snapshot = InstanceSnapshot::new()
|
||||
.name(name)
|
||||
.class_name("LocalizationTable")
|
||||
.properties([("Contents".to_owned(), table_contents.into())])
|
||||
.property(ustr("Contents"), table_contents)
|
||||
.metadata(
|
||||
InstanceMetadata::new()
|
||||
.instigating_source(path)
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::path::Path;
|
||||
|
||||
use anyhow::Context;
|
||||
use memofs::{IoResultExt, Vfs};
|
||||
use rbx_dom_weak::ustr;
|
||||
|
||||
use crate::{
|
||||
lua_ast::{Expression, Statement},
|
||||
@@ -23,14 +24,12 @@ pub fn snapshot_json(
|
||||
|
||||
let as_lua = json_to_lua(value).to_string();
|
||||
|
||||
let properties = [("Source".to_owned(), as_lua.into())];
|
||||
|
||||
let meta_path = path.with_file_name(format!("{}.meta.json", name));
|
||||
|
||||
let mut snapshot = InstanceSnapshot::new()
|
||||
.name(name)
|
||||
.class_name("ModuleScript")
|
||||
.properties(properties)
|
||||
.property(ustr("Source"), as_lua)
|
||||
.metadata(
|
||||
InstanceMetadata::new()
|
||||
.instigating_source(path)
|
||||
|
||||
@@ -2,7 +2,10 @@ use std::{borrow::Cow, collections::HashMap, path::Path, str};
|
||||
|
||||
use anyhow::Context;
|
||||
use memofs::Vfs;
|
||||
use rbx_dom_weak::types::{Attributes, Ref};
|
||||
use rbx_dom_weak::{
|
||||
types::{Attributes, Ref},
|
||||
HashMapExt as _, Ustr, UstrMap,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{
|
||||
@@ -68,7 +71,7 @@ struct JsonModel {
|
||||
name: Option<String>,
|
||||
|
||||
#[serde(alias = "ClassName")]
|
||||
class_name: String,
|
||||
class_name: Ustr,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
id: Option<String>,
|
||||
@@ -82,10 +85,10 @@ struct JsonModel {
|
||||
|
||||
#[serde(
|
||||
alias = "Properties",
|
||||
default = "HashMap::new",
|
||||
default = "UstrMap::new",
|
||||
skip_serializing_if = "HashMap::is_empty"
|
||||
)]
|
||||
properties: HashMap<String, UnresolvedValue>,
|
||||
properties: UstrMap<UnresolvedValue>,
|
||||
|
||||
#[serde(default = "HashMap::new", skip_serializing_if = "HashMap::is_empty")]
|
||||
attributes: HashMap<String, UnresolvedValue>,
|
||||
@@ -93,7 +96,7 @@ struct JsonModel {
|
||||
|
||||
impl JsonModel {
|
||||
fn into_snapshot(self) -> anyhow::Result<InstanceSnapshot> {
|
||||
let name = self.name.unwrap_or_else(|| self.class_name.clone());
|
||||
let name = self.name.unwrap_or_else(|| self.class_name.to_owned());
|
||||
let class_name = self.class_name;
|
||||
|
||||
let mut children = Vec::with_capacity(self.children.len());
|
||||
@@ -101,7 +104,7 @@ impl JsonModel {
|
||||
children.push(child.into_snapshot()?);
|
||||
}
|
||||
|
||||
let mut properties = HashMap::with_capacity(self.properties.len());
|
||||
let mut properties = UstrMap::with_capacity(self.properties.len());
|
||||
for (key, unresolved) in self.properties {
|
||||
let value = unresolved.resolve(&class_name, &key)?;
|
||||
properties.insert(key, value);
|
||||
@@ -122,7 +125,7 @@ impl JsonModel {
|
||||
snapshot_id: Ref::none(),
|
||||
metadata: Default::default(),
|
||||
name: Cow::Owned(name),
|
||||
class_name: Cow::Owned(class_name),
|
||||
class_name,
|
||||
properties,
|
||||
children,
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::{collections::HashMap, path::Path, str};
|
||||
use std::{path::Path, str};
|
||||
|
||||
use memofs::{IoResultExt, Vfs};
|
||||
use rbx_dom_weak::types::Enum;
|
||||
use rbx_dom_weak::{types::Enum, ustr, HashMapExt as _, UstrMap};
|
||||
|
||||
use crate::snapshot::{InstanceContext, InstanceMetadata, InstanceSnapshot};
|
||||
|
||||
@@ -42,12 +42,12 @@ pub fn snapshot_lua(
|
||||
let contents = vfs.read_to_string_lf_normalized(path)?;
|
||||
let contents_str = contents.as_str();
|
||||
|
||||
let mut properties = HashMap::with_capacity(2);
|
||||
properties.insert("Source".to_owned(), contents_str.into());
|
||||
let mut properties = UstrMap::with_capacity(2);
|
||||
properties.insert(ustr("Source"), contents_str.into());
|
||||
|
||||
if let Some(run_context) = run_context {
|
||||
properties.insert(
|
||||
"RunContext".to_owned(),
|
||||
ustr("RunContext"),
|
||||
Enum::from_u32(run_context.to_owned()).into(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::{borrow::Cow, collections::HashMap, path::PathBuf};
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use anyhow::{format_err, Context};
|
||||
use rbx_dom_weak::types::Attributes;
|
||||
use rbx_dom_weak::{types::Attributes, Ustr, UstrMap};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{resolution::UnresolvedValue, snapshot::InstanceSnapshot, RojoRef};
|
||||
@@ -23,7 +23,7 @@ pub struct AdjacentMetadata {
|
||||
pub ignore_unknown_instances: Option<bool>,
|
||||
|
||||
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
|
||||
pub properties: HashMap<String, UnresolvedValue>,
|
||||
pub properties: UstrMap<UnresolvedValue>,
|
||||
|
||||
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
|
||||
pub attributes: HashMap<String, UnresolvedValue>,
|
||||
@@ -117,13 +117,13 @@ pub struct DirectoryMetadata {
|
||||
pub ignore_unknown_instances: Option<bool>,
|
||||
|
||||
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
|
||||
pub properties: HashMap<String, UnresolvedValue>,
|
||||
pub properties: UstrMap<UnresolvedValue>,
|
||||
|
||||
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
|
||||
pub attributes: HashMap<String, UnresolvedValue>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub class_name: Option<String>,
|
||||
pub class_name: Option<Ustr>,
|
||||
|
||||
#[serde(skip)]
|
||||
pub path: PathBuf,
|
||||
@@ -161,7 +161,7 @@ impl DirectoryMetadata {
|
||||
));
|
||||
}
|
||||
|
||||
snapshot.class_name = Cow::Owned(class_name);
|
||||
snapshot.class_name = class_name;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
use std::{borrow::Cow, collections::HashMap, path::Path};
|
||||
use std::{borrow::Cow, path::Path};
|
||||
|
||||
use anyhow::{bail, Context};
|
||||
use memofs::Vfs;
|
||||
use rbx_dom_weak::types::{Attributes, Ref};
|
||||
use rbx_dom_weak::{
|
||||
types::{Attributes, Ref},
|
||||
ustr, HashMapExt as _, Ustr, UstrMap,
|
||||
};
|
||||
use rbx_reflection::ClassTag;
|
||||
|
||||
use crate::{
|
||||
@@ -88,14 +91,10 @@ pub fn snapshot_project_node(
|
||||
) -> anyhow::Result<Option<InstanceSnapshot>> {
|
||||
let project_folder = project_path.parent().unwrap();
|
||||
|
||||
let class_name_from_project = node
|
||||
.class_name
|
||||
.as_ref()
|
||||
.map(|name| Cow::Owned(name.clone()));
|
||||
let mut class_name_from_path = None;
|
||||
|
||||
let name = Cow::Owned(instance_name.to_owned());
|
||||
let mut properties = HashMap::new();
|
||||
let mut properties = UstrMap::new();
|
||||
let mut children = Vec::new();
|
||||
let mut metadata = InstanceMetadata::new().context(context);
|
||||
|
||||
@@ -136,7 +135,7 @@ pub fn snapshot_project_node(
|
||||
let class_name_from_inference = infer_class_name(&name, parent_class);
|
||||
|
||||
let class_name = match (
|
||||
class_name_from_project,
|
||||
node.class_name,
|
||||
class_name_from_path,
|
||||
class_name_from_inference,
|
||||
&node.path,
|
||||
@@ -249,7 +248,7 @@ pub fn snapshot_project_node(
|
||||
_ => {}
|
||||
}
|
||||
|
||||
properties.insert(key.clone(), value);
|
||||
properties.insert(*key, value);
|
||||
}
|
||||
|
||||
if !node.attributes.is_empty() {
|
||||
@@ -304,7 +303,7 @@ pub fn snapshot_project_node(
|
||||
}))
|
||||
}
|
||||
|
||||
fn infer_class_name(name: &str, parent_class: Option<&str>) -> Option<Cow<'static, str>> {
|
||||
fn infer_class_name(name: &str, parent_class: Option<&str>) -> Option<Ustr> {
|
||||
// If className wasn't defined from another source, we may be able
|
||||
// to infer one.
|
||||
|
||||
@@ -317,18 +316,18 @@ fn infer_class_name(name: &str, parent_class: Option<&str>) -> Option<Cow<'stati
|
||||
let descriptor = rbx_reflection_database::get().classes.get(name)?;
|
||||
|
||||
if descriptor.tags.contains(&ClassTag::Service) {
|
||||
return Some(Cow::Owned(name.to_owned()));
|
||||
return Some(ustr(name));
|
||||
}
|
||||
} else if parent_class == "StarterPlayer" {
|
||||
// StarterPlayer has two special members with their own classes.
|
||||
|
||||
if name == "StarterPlayerScripts" || name == "StarterCharacterScripts" {
|
||||
return Some(Cow::Owned(name.to_owned()));
|
||||
return Some(ustr(name));
|
||||
}
|
||||
} else if parent_class == "Workspace" {
|
||||
// Workspace has a special Terrain class inside it
|
||||
if name == "Terrain" {
|
||||
return Some(Cow::Owned(name.to_owned()));
|
||||
return Some(ustr(name));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::path::Path;
|
||||
|
||||
use anyhow::Context;
|
||||
use memofs::{IoResultExt, Vfs};
|
||||
use rbx_dom_weak::ustr;
|
||||
|
||||
use crate::{
|
||||
lua_ast::{Expression, Statement},
|
||||
@@ -23,14 +24,12 @@ pub fn snapshot_toml(
|
||||
|
||||
let as_lua = toml_to_lua(value).to_string();
|
||||
|
||||
let properties = [("Source".to_owned(), as_lua.into())];
|
||||
|
||||
let meta_path = path.with_file_name(format!("{}.meta.json", name));
|
||||
|
||||
let mut snapshot = InstanceSnapshot::new()
|
||||
.name(name)
|
||||
.class_name("ModuleScript")
|
||||
.properties(properties)
|
||||
.property(ustr("Source"), as_lua)
|
||||
.metadata(
|
||||
InstanceMetadata::new()
|
||||
.instigating_source(path)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::{path::Path, str};
|
||||
|
||||
use memofs::{IoResultExt, Vfs};
|
||||
use rbx_dom_weak::ustr;
|
||||
|
||||
use crate::snapshot::{InstanceContext, InstanceMetadata, InstanceSnapshot};
|
||||
|
||||
@@ -15,14 +16,12 @@ pub fn snapshot_txt(
|
||||
let contents = vfs.read_to_string(path)?;
|
||||
let contents_str = contents.as_str();
|
||||
|
||||
let properties = [("Value".to_owned(), contents_str.into())];
|
||||
|
||||
let meta_path = path.with_file_name(format!("{}.meta.json", name));
|
||||
|
||||
let mut snapshot = InstanceSnapshot::new()
|
||||
.name(name)
|
||||
.class_name("StringValue")
|
||||
.properties(properties)
|
||||
.property(ustr("Value"), contents_str)
|
||||
.metadata(
|
||||
InstanceMetadata::new()
|
||||
.instigating_source(path)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
//! Utiilty that helps redact nondeterministic information from trees so that
|
||||
//! they can be part of snapshot tests.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use rbx_dom_weak::types::{Ref, Variant};
|
||||
use rbx_dom_weak::{
|
||||
types::{Ref, Variant},
|
||||
Ustr, UstrMap,
|
||||
};
|
||||
use rojo_insta_ext::RedactionMap;
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -34,8 +35,8 @@ pub fn intern_tree(tree: &RojoTree, redactions: &mut RedactionMap) {
|
||||
struct InstanceView {
|
||||
id: Ref,
|
||||
name: String,
|
||||
class_name: String,
|
||||
properties: HashMap<String, Variant>,
|
||||
class_name: Ustr,
|
||||
properties: UstrMap<Variant>,
|
||||
metadata: InstanceMetadata,
|
||||
children: Vec<InstanceView>,
|
||||
}
|
||||
@@ -46,7 +47,7 @@ fn extract_instance_view(tree: &RojoTree, id: Ref) -> InstanceView {
|
||||
InstanceView {
|
||||
id: instance.id(),
|
||||
name: instance.name().to_owned(),
|
||||
class_name: instance.class_name().to_owned(),
|
||||
class_name: instance.class_name(),
|
||||
properties: instance.properties().clone(),
|
||||
metadata: instance.metadata().clone(),
|
||||
children: instance
|
||||
|
||||
@@ -280,7 +280,7 @@ impl ApiService {
|
||||
/// If this instance is represented by a script, try to find the correct .lua or .luau
|
||||
/// file to open to edit it.
|
||||
fn pick_script_path(instance: InstanceWithMeta<'_>) -> Option<PathBuf> {
|
||||
match instance.class_name() {
|
||||
match instance.class_name().as_str() {
|
||||
"Script" | "LocalScript" | "ModuleScript" => {}
|
||||
_ => return None,
|
||||
}
|
||||
|
||||
@@ -7,7 +7,10 @@ use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
};
|
||||
|
||||
use rbx_dom_weak::types::{Ref, Variant, VariantType};
|
||||
use rbx_dom_weak::{
|
||||
types::{Ref, Variant, VariantType},
|
||||
Ustr, UstrMap,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
@@ -84,12 +87,12 @@ impl<'a> SubscribeMessage<'a> {
|
||||
pub struct InstanceUpdate {
|
||||
pub id: Ref,
|
||||
pub changed_name: Option<String>,
|
||||
pub changed_class_name: Option<String>,
|
||||
pub changed_class_name: Option<Ustr>,
|
||||
|
||||
// TODO: Transform from HashMap<String, Option<_>> to something else, since
|
||||
// TODO: Transform from UstrMap<String, Option<_>> to something else, since
|
||||
// null will get lost when decoding from JSON in some languages.
|
||||
#[serde(default)]
|
||||
pub changed_properties: HashMap<String, Option<Variant>>,
|
||||
pub changed_properties: UstrMap<Option<Variant>>,
|
||||
pub changed_metadata: Option<InstanceMetadata>,
|
||||
}
|
||||
|
||||
@@ -113,8 +116,8 @@ pub struct Instance<'a> {
|
||||
pub id: Ref,
|
||||
pub parent: Ref,
|
||||
pub name: Cow<'a, str>,
|
||||
pub class_name: Cow<'a, str>,
|
||||
pub properties: HashMap<String, Cow<'a, Variant>>,
|
||||
pub class_name: Ustr,
|
||||
pub properties: UstrMap<Cow<'a, Variant>>,
|
||||
pub children: Cow<'a, [Ref]>,
|
||||
pub metadata: Option<InstanceMetadata>,
|
||||
}
|
||||
@@ -125,14 +128,14 @@ impl Instance<'_> {
|
||||
.properties()
|
||||
.iter()
|
||||
.filter(|(_key, value)| property_filter(Some(value)))
|
||||
.map(|(key, value)| (key.clone(), Cow::Borrowed(value)))
|
||||
.map(|(key, value)| (*key, Cow::Borrowed(value)))
|
||||
.collect();
|
||||
|
||||
Instance {
|
||||
id: source.id(),
|
||||
parent: source.parent(),
|
||||
name: Cow::Borrowed(source.name()),
|
||||
class_name: Cow::Borrowed(source.class_name()),
|
||||
class_name: source.class_name(),
|
||||
properties,
|
||||
children: Cow::Borrowed(source.children()),
|
||||
metadata: Some(InstanceMetadata::from_rojo_metadata(source.metadata())),
|
||||
|
||||
@@ -119,7 +119,7 @@ impl UiService {
|
||||
.map(|(key, value)| {
|
||||
html! {
|
||||
<div class="instance-property" title={ Self::display_value(value) }>
|
||||
{ key.clone() } ": " { format!("{:?}", value.ty()) }
|
||||
{ key.as_str() } ": " { format!("{:?}", value.ty()) }
|
||||
</div>
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user