mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-20 20:55:50 +00:00
265 lines
5.6 KiB
Lua
265 lines
5.6 KiB
Lua
local Reconciler = {}
|
|
|
|
--[[
|
|
The set of file names that should pass as init files
|
|
These files usurp their parents.
|
|
]]
|
|
local initNames = {
|
|
["init.lua"] = true,
|
|
["init.server.lua"] = true,
|
|
["init.client.lua"] = true,
|
|
}
|
|
|
|
local function isInit(item, itemFileName)
|
|
if item and item.type == "dir" then
|
|
return
|
|
end
|
|
|
|
return initNames[itemFileName] or false
|
|
end
|
|
|
|
--[[
|
|
Determines if the given VFS item has an init file. Yields information about
|
|
the file.
|
|
]]
|
|
local function findInit(item)
|
|
if item.type ~= "dir" then
|
|
return nil, nil
|
|
end
|
|
|
|
for childFileName, childItem in pairs(item.children) do
|
|
if isInit(childItem, childFileName) then
|
|
return childItem, childFileName
|
|
end
|
|
end
|
|
|
|
return nil, nil
|
|
end
|
|
|
|
--[[
|
|
Given a VFS item, returns a Name and ClassName for a corresponding Roblox
|
|
instance.
|
|
|
|
Doesn't take into account init files.
|
|
]]
|
|
local function itemToName(item, fileName)
|
|
if item and item.type == "dir" then
|
|
return fileName, "Folder"
|
|
elseif item and item.type == "file" or not item then
|
|
if fileName:find("%.server%.lua$") then
|
|
return fileName:match("^(.-)%.server%.lua$"), "Script"
|
|
elseif fileName:find("%.client%.lua$") then
|
|
return fileName:match("^(.-)%.client%.lua$"), "LocalScript"
|
|
elseif fileName:find("%.lua") then
|
|
return fileName:match("^(.-)%.lua$"), "ModuleScript"
|
|
else
|
|
return fileName, "StringValue"
|
|
end
|
|
else
|
|
error("unknown item type " .. tostring(item.type))
|
|
end
|
|
end
|
|
|
|
--[[
|
|
Given a VFS item, assigns all relevant values (except Name!) to a Roblox
|
|
instance.
|
|
]]
|
|
local function setValues(rbx, item, fileName)
|
|
local _, className = itemToName(item, fileName)
|
|
|
|
if className:find("Script") then
|
|
rbx.Source = item.contents
|
|
else
|
|
rbx.Value = item.contents
|
|
end
|
|
end
|
|
|
|
function Reconciler._reifyShallow(item, fileName)
|
|
if item.type == "dir" then
|
|
local initItem, initFileName = findInit(item)
|
|
|
|
if initItem then
|
|
local rbx = Reconciler._reify(initItem, initFileName)
|
|
rbx.Name = fileName
|
|
|
|
return rbx
|
|
else
|
|
local rbx = Instance.new("Folder")
|
|
rbx.Name = fileName
|
|
|
|
return rbx
|
|
end
|
|
elseif item.type == "file" then
|
|
local objectName, className = itemToName(item, fileName)
|
|
|
|
local rbx = Instance.new(className)
|
|
rbx.Name = objectName
|
|
|
|
setValues(rbx, item, fileName)
|
|
|
|
return rbx
|
|
else
|
|
error("unknown item type " .. tostring(item.type))
|
|
end
|
|
end
|
|
|
|
--[[
|
|
Construct a new Roblox instance tree that corresponds to the given VFS item.
|
|
]]
|
|
function Reconciler._reify(item, fileName, parent)
|
|
local rbx = Reconciler._reifyShallow(item, fileName)
|
|
|
|
if item.type == "dir" then
|
|
for childFileName, childItem in pairs(item.children) do
|
|
if not isInit(childItem, childFileName) then
|
|
local childRbx = Reconciler._reify(childItem, childFileName)
|
|
childRbx.Parent = rbx
|
|
end
|
|
end
|
|
end
|
|
|
|
rbx.Parent = parent
|
|
|
|
return rbx
|
|
end
|
|
|
|
function Reconciler.reconcile(rbx, item, fileName, parent)
|
|
-- Item was deleted!
|
|
if not item then
|
|
if isInit(item, fileName) then
|
|
if not parent then
|
|
return
|
|
end
|
|
|
|
-- Un-usurp parent!
|
|
local newParent = Instance.new("Folder")
|
|
newParent.Name = parent.Name
|
|
|
|
for _, child in ipairs(parent:GetChildren()) do
|
|
child.Parent = newParent
|
|
end
|
|
|
|
newParent.Parent = parent.Parent
|
|
parent:Destroy()
|
|
|
|
return
|
|
else
|
|
if rbx then
|
|
rbx:Destroy()
|
|
end
|
|
|
|
return
|
|
end
|
|
end
|
|
|
|
if item.type == "dir" then
|
|
-- Folder was created!
|
|
if not rbx then
|
|
return Reconciler._reify(item, fileName, parent)
|
|
end
|
|
|
|
local initItem, initFileName = findInit(item)
|
|
|
|
if initItem then
|
|
local _, initClassName = itemToName(initItem, initFileName)
|
|
|
|
if rbx.ClassName == initClassName then
|
|
setValues(rbx, initItem, initFileName)
|
|
else
|
|
rbx:Destroy()
|
|
return Reconciler._reify(item, fileName, parent)
|
|
end
|
|
else
|
|
if rbx.ClassName ~= "Folder" then
|
|
rbx:Destroy()
|
|
return Reconciler._reify(item, fileName, parent)
|
|
end
|
|
end
|
|
|
|
local visitedChildren = {}
|
|
|
|
for childFileName, childItem in pairs(item.children) do
|
|
if not isInit(childItem, childFileName) then
|
|
local childName = itemToName(childItem, childFileName)
|
|
|
|
visitedChildren[childName] = true
|
|
|
|
Reconciler.reconcile(rbx:FindFirstChild(childName), childItem, childFileName, rbx)
|
|
end
|
|
end
|
|
|
|
for _, childRbx in ipairs(rbx:GetChildren()) do
|
|
-- Child was deleted!
|
|
if not visitedChildren[childRbx.Name] then
|
|
childRbx:Destroy()
|
|
end
|
|
end
|
|
|
|
return rbx
|
|
elseif item.type == "file" then
|
|
if isInit(item, fileName) then
|
|
-- Usurp our container!
|
|
local _, className = itemToName(item, fileName)
|
|
|
|
if parent.ClassName == className then
|
|
rbx = parent
|
|
else
|
|
rbx = Reconciler._reify(item, fileName, parent.Parent)
|
|
rbx.Name = parent.Name
|
|
|
|
for _, child in ipairs(parent:GetChildren()) do
|
|
child.Parent = rbx
|
|
end
|
|
|
|
parent:Destroy()
|
|
end
|
|
|
|
setValues(rbx, item, fileName)
|
|
|
|
return rbx
|
|
else
|
|
if not rbx then
|
|
return Reconciler._reify(item, fileName, parent)
|
|
end
|
|
|
|
local _, className = itemToName(item, fileName)
|
|
|
|
if rbx.ClassName ~= className then
|
|
rbx:Destroy()
|
|
return Reconciler._reify(item, fileName, parent)
|
|
end
|
|
|
|
setValues(rbx, item, fileName)
|
|
|
|
return rbx
|
|
end
|
|
else
|
|
error("unknown item type " .. tostring(item.type))
|
|
end
|
|
end
|
|
|
|
function Reconciler.reconcileRoute(route, item)
|
|
local location = game
|
|
|
|
for i = 1, #route - 1 do
|
|
local piece = route[i]
|
|
local newLocation = location:FindFirstChild(piece)
|
|
|
|
if not newLocation then
|
|
newLocation = Instance.new("Folder")
|
|
newLocation.Name = piece
|
|
newLocation.Parent = location
|
|
end
|
|
|
|
location = newLocation
|
|
end
|
|
|
|
local fileName = route[#route]
|
|
|
|
local name = itemToName(item, fileName)
|
|
local rbx = location:FindFirstChild(name)
|
|
Reconciler.reconcile(rbx, item, fileName, location)
|
|
end
|
|
|
|
return Reconciler
|