FDK is a package management class-based system for better code organisation. FDK is nearly to the first release and we wanted to see if there’s any changes we should make to improve FDK before we release it. We’re working on a dependency system, plugin for installing, and a logging package that is nearly finished. Any ideas or improvements would be helpful! (https://github.com/TheFlamingBlaster/FDK)
(@froghopperjacob (me) and @TheFlamingBlaster)
BaseClass:
--[[
BaseClass module, used as a root for all other classes. Internally indexed as BaseClass
Licenced under the terms at: https://www.apache.org/licenses/LICENSE-2.0.txt
--]]
local BaseClass = { }
local internal = { }
internal.classes = { }
--[[
Function: Checks the object against any types given. cleans code up
Arguments: object - check against, ... - types
]]
local function checkTypes(object, ...)
for _, check in pairs({ ... }) do
if (typeof(object) == check) then
return true
end
end
return false
end
--[[
Function: Tries to get the metamethod of a metatable
Arguments: object - class or any table /shrug, try - what is being tried
]]
local function tryGetMetatable(object, try)
local _, got = pcall(function()
return getmetatable(object)[try]
end)
return got
end
--[[
Function: Creates a proxy interface for locked classes
Arguments: object - class or any table /shrug
]]
local function createProxyInterface(object)
local cachedObject = { } -- should cached be a weak table to prevent garbage collection?
return setmetatable({ }, {
__index = function(self, index)
if (typeof(object[index]) == "table") then
if (cachedObject[index] == nil) then
cachedObject[index] = createProxyInterface(object[index])
end
return cachedObject[index]
end
return object[index]
end,
__newindex = function(self, index, value)
return error("[BaseClass - LOCKED] This table is locked")
end,
__call = function(self, ...)
return object(...):lock()
end,
__tostring = function(self)
return tostring(object)
end,
--Class Metamethods
__concact = tryGetMetatable(object, "__concat"),
__unm = tryGetMetatable(object, "__unm"),
__add = tryGetMetatable(object, "__add"),
__sub = tryGetMetatable(object, "__sub"),
__mul = tryGetMetatable(object, "__mul"),
__div = tryGetMetatable(object, "__div"),
__mod = tryGetMetatable(object, "__mod"),
__pow = tryGetMetatable(object, "__pow"),
__eq = tryGetMetatable(object, "__eq"),
__lt = tryGetMetatable(object, "__lt"),
__le = tryGetMetatable(object, "__le"),
__gc = tryGetMetatable(object, "__gc"),
__len = tryGetMetatable(object, "__len"),
__mode = tryGetMetatable(object, "__mode"),
--Class Metamethods
__metatable = "B-Locked."
})
end
--[[
Function: Checks if the classes are the exact same
Arguments: self - the class, value - the other class
]]
internal.__eq = function(self, value)
return internal.classes[value] and self:isA(value["className"]) or false
end
--[[
Function: When the init function is called this is called and returns an extended class
Arguments: self - the class, ... - values supplied by the user
]]
internal.__call = function(self, ...)
local className = self.className
local initFunction = self[className]
if (initFunction and checkTypes(initFunction, "function")) then
return initFunction(self:Extend(className), ...)
end
return error("[BaseClass - CONSTRUCTOR]: No constructor function found for class " .. className)
end
local external = createProxyInterface(BaseClass)
--[[
Function: Creates a new class from the class name
Arguments: self - the module, className - the class name
]]
BaseClass.new = function(self, className)
if (not checkTypes(className, "string") or string.len(className) == 0) then
return error("[FDK - CLASS INITIALISATION]: Expected string, got " .. className .. ".")
end
local newClass, classProperties = { }, {
["className"] = className,
["inherits"] = {
["BaseClass"] = external
}
}
local externalClassProperties, classString =
createProxyInterface(classProperties),
className .. ": " ..tostring(newClass):sub(7)
internal.classes[newClass] = classProperties
newClass.__properties = externalClassProperties
return setmetatable(newClass, {
__index = function(self, index)
if (rawget(newClass, index)) then
return rawget(newClass, index)
end
if (classProperties[index]) then
return externalClassProperties[index]
end
if (BaseClass[index]) then
return BaseClass[index]
end
end,
__newindex = function(self, index, value)
if (string.sub(index, 1, string.len("__")) == "__" and checkTypes(value, "function", "string")) then
getmetatable(self)[index] = value
end
return rawset(self, index, value)
end,
__tostring = function(self)
return classString
end,
__eq = internal.__eq,
__call = internal.__call
})
end
--[[
Function: Creates a new class and allows access to the inherited class
Arguments: extender - the original class, className - the new class name
]]
BaseClass.extend = function(extender, className)
local newClass, properties =
BaseClass:New(className), internal.classes[extender]
internal.classes[newClass].inherits = properties.inherits
internal.classes[newClass].inherits[properties.className] = extender
getmetatable(newClass).__index = function(self, k)
if (rawget(newClass, k)) then
return rawget(newClass, k)
end
if (extender[k]) then
return extender[k]
end
if (BaseClass[k]) then
return BaseClass[k]
end
end
return newClass
end
--[[
Function: Returns a locked class
Arguments: self - the class
]]
BaseClass.lock = function(self)
return createProxyInterface(self)
end
--[[
Function: Tells if a class is inheritied by antoher
Arguments: self - the class, className - the other class
]]
BaseClass.isA = function(self, className)
return internal.classes[self].inherits[className] ~= nil
end
--[[
Function: Tells if a class is registered in internals
Arguments: self - this class, class - checking another class
Explain: If you provide the argument class it will check that instead of this class, self
]]
BaseClass.registered = function(self, class)
return internal.classes[class ~= nil and class or self] ~= nil
end
--Legacy Support
BaseClass.Registered = BaseClass.registered
BaseClass.IsA = BaseClass.isA
BaseClass.Lock = BaseClass.lock
BaseClass.Extend = BaseClass.extend
BaseClass.New = BaseClass.new
--Legacy Support
return external
FDK:
--[[
Main module for FDK package management.
Licenced under the terms at: https://www.apache.org/licenses/LICENSE-2.0.txt
--]]
local BaseClass
local packages
-- This is here checking if its Lemur and where the packages/baseclass are
if (__LEMUR__ == nil) then
packages = (game:GetService("RunService"):IsClient() and
game:GetService("ReplicatedStorage"):FindFirstChild("ClientPackages") or
game:GetService("ServerScriptService"):FindFirstChild("ServerPackages"))
BaseClass = require(script.BaseClass)
else
packages = script.Parent.Parent.tests
BaseClass = require(script.Parent.BaseClass)
end
local FDK = BaseClass:new("Flaming Development Toolkit")
local external = FDK:lock()
--[[
Function: Checks the object against any types given. cleans code up
Arguments: object - check against, ... - types
]]
local function checkTypes(object, ...)
local typeOfObject = typeof(object)
for _, check in pairs({ ... }) do
if (typeOfObject == check) then
return true
end
end
return false
end
--[[
Function: Creates a new class from the class name
Arguments: self - FDK, importString - what is wanted to be imported
]]
FDK.import = function(self, importString)
if (not checkTypes(importString, "string") or string.len(importString) == 0) then
return error("[FDK - PACKAGE MANAGER] Expected string, got "..typeof(importString))
end
if (importString == "FDK") then
return external
elseif (importString == "BaseClass" or importString == "Class") then
return BaseClass
end
local currentIndex, splitImportString, toRequire =
packages, { }, nil
for directory in string.gmatch(importString, "%a+") do
table.insert(splitImportString, directory)
end
for _, directory in pairs(splitImportString) do
if (currentIndex:FindFirstChild(directory)) then
currentIndex = currentIndex[directory]
else
return error("[FDK - PACKAGE MANAGER] Package " .. directory .. " does not exist.")
end
end
if (currentIndex:IsA("NumberValue") or currentIndex:IsA("IntValue")) then
toRequire = currentIndex.Value
elseif (currentIndex:IsA("ModuleScript")) then
toRequire = currentIndex
end
if (toRequire == nil) then
return error("[FDK - PACKAGE MANAGER] Package does not exist.")
end
local class = require(toRequire)
if (not checkTypes(class, "table", "function")) then
return error("[FDK - PACKAGE MANAGER] Expected function or table, got "
.. typeof(class) .. " while initalizing class module.")
end
self:wrapEnvironment(class)
return class(), currentIndex.Name -- Should we be calling the class?
end
--[[
Function: Wraps the provided enviroment with useful FDK functions
Arguments: self - FDK, value - table/function given for the enviroment
]]
FDK.wrapEnvironment = function(self, value)
local useValue = (value == nil and self or value)
local functionEnviorment
if (checkTypes(useValue, "function")) then
functionEnviorment = getfenv(useValue)
elseif (checkTypes(useValue, "table")) then
functionEnviorment = useValue
end
if (functionEnviorment == nil) then
return error("[FDK - PACKAGE MANAGER] Expected function or table, got " .. typeof(functionEnviorment) .. ".")
end
functionEnviorment.import = function(importString)
local class, name = self:import(importString)
return class, name
end
functionEnviorment.BaseClass = BaseClass
functionEnviorment.new = FDK.new
--Legacy Support
functionEnviorment.Class = BaseClass
functionEnviorment.New = FDK.new
--Legacy Support
end
--Legacy Support
FDK.WrapEnv = FDK.wrapEnvironment
FDK.Import = FDK.import
--Legacy Support
_G.FDK = external
return external
EDIT: Add plugin