Skip to content

Instantly share code, notes, and snippets.

@Validark
Created July 18, 2020 15:56
Show Gist options
  • Select an option

  • Save Validark/333c0d1d551ef6021bcc161067d4d1dd to your computer and use it in GitHub Desktop.

Select an option

Save Validark/333c0d1d551ef6021bcc161067d4d1dd to your computer and use it in GitHub Desktop.
const HttpService = game.GetService("HttpService");
export async function getRequest(...[url, nocache, headers]: Parameters<HttpService["GetAsync"]>) {
return HttpService.GetAsync(url, nocache, headers);
}
export interface ReferenceData {
readonly owner: string;
readonly repository: string;
readonly branch?: string;
}
export async function getRefs({ owner, repository, branch = "master" }: ReferenceData) {
return getRequest(`https://api.github.com/repos/${owner}/${repository}/git/refs/heads/${branch}`).then(
(str) =>
HttpService.JSONDecode(str) as {
ref: string;
node_id: string;
url: string;
object: { sha: string; type: string; url: string };
},
);
}
interface TreeNodeMaster {
/** The path to this node.
* e.g. lib/init.lua
*/
path: string;
/** The file mode:
* 100644 for file (blob)
* 100755 for executable (blob)
* 040000 for subdirectory (tree)
* 160000 for submodule (commit)
* 120000 for a blob that specifies the path of a symlink
*/
mode: "100644" | "100755" | "040000" | "160000" | "120000";
/** The type of Node this is. */
type: "blob" | "tree" | "commit";
/** The SHA1 checksum ID of the node. */
sha: string;
/** How much memory this node takes up. */
size: number;
/** The URL of this node. */
url: string;
}
interface Blob extends TreeNodeMaster {
type: "blob";
}
interface Tree extends Omit<TreeNodeMaster, "size"> {
type: "tree";
}
interface Commit extends Omit<TreeNodeMaster, "size" | "url"> {
type: "commit";
}
type TreeNode = Blob | Tree | Commit;
export async function getRepoTree(data: ReferenceData) {
return getRefs(data)
.then(({ object: { sha } }) =>
getRequest(`https://api.github.com/repos/${data.owner}/${data.repository}/git/trees/${sha}?recursive=true`),
)
.then(
(str) =>
HttpService.JSONDecode(str) as {
sha: string;
url: string;
tree: Array<TreeNode>;
truncated: boolean;
},
);
}
export type RepoMap = Map<string, string | RepoMap>;
export async function getProcessedRepoTree(referenceData: ReferenceData) {
return getRepoTree(referenceData).then(({ tree }) => {
const { owner, repository, branch = "master" } = referenceData;
const map: RepoMap = new Map();
for (const { path, type: myType } of tree) {
const items = new Array<string>();
for (const [item] of path.gmatch("[^/]+")) {
items.push(item);
}
const last = items.pop()!;
let current: RepoMap = map;
for (const item of items) {
const nextItem = current.get(item);
if (nextItem === undefined || typeIs(nextItem, "string")) error("Houston, we have a problem!");
current = nextItem;
}
switch (myType) {
case "tree":
current.set(last, new Map());
break;
case "blob":
current.set(last, `https://raw.githubusercontent.com/${owner}/${repository}/${branch}/${path}`);
break;
case "commit":
// just ignore submodules for now? I don't really know how this should even be handled
break;
}
}
return map;
});
}
const scriptExtensions = [
[".spec.lua", ""],
[".spec.server.lua", ""],
[".spec.client.lua", ""],
[".server.lua", "Script"],
[".script.lua", "Script"],
[".client.lua", "LocalScript"],
[".loc.lua", "LocalScript"],
[".local.lua", "LocalScript"],
[".mod.lua", "ModuleScript"],
[".module.lua", "ModuleScript"],
[".lua", "ModuleScript"],
] as const;
function instantiateScript(
link: string,
name: string,
scriptType: "Script" | "LocalScript" | "ModuleScript",
parent: Instance | undefined,
) {
const script = new Instance(scriptType);
script.Name = name;
script.Parent = parent;
getRequest(link).then((source) => {
// script.Source = source;
const str = new Instance("StringValue");
str.Name = "Source";
str.Value = source;
str.Parent = script;
});
return script;
}
function instantiateTree(tree: RepoMap, parent: Instance = new Instance("Folder")) {
for (const [name, link] of tree) {
// file
if (typeIs(link, "string")) {
for (const [scriptExtension, scriptType] of scriptExtensions) {
if (link.endsWith(scriptExtension) && scriptType) {
instantiateScript(link, name.slice(0, -scriptExtension.size()), scriptType, parent);
break;
}
}
}
// folder
else {
const repoMap = link;
let folder: Folder | Script | ModuleScript | undefined;
for (const [scriptExtension, scriptType] of scriptExtensions) {
const repoName = "init" + scriptExtension;
const initFile = repoMap.get(repoName);
if (typeIs(initFile, "string") && scriptType) {
if (folder) error("Found multiple init files?");
repoMap.delete(repoName);
folder = instantiateScript(initFile, name, scriptType, parent);
}
}
if (folder === undefined) {
folder = new Instance("Folder");
folder.Name = name;
folder.Parent = parent;
}
instantiateTree(repoMap, folder);
if (folder.IsA("Folder") && folder.GetChildren().size() === 0) {
folder.Destroy();
}
}
}
return parent;
}
getProcessedRepoTree({
owner: "Quenty",
repository: "NevermoreEngine",
branch: "version2",
}).then((tree) => {
let current = tree.get("loader");
if (current && !typeIs(current, "string")) {
current = current.get("ReplicatedStorage");
if (current && !typeIs(current, "string")) {
for (const child of instantiateTree(current).GetChildren()) {
child.Parent = game.GetService("ReplicatedStorage");
}
}
}
current = tree.get("Modules");
if (current && !typeIs(current, "string")) {
const modules = instantiateTree(current);
modules.Name = "Nevermore";
modules.Parent = game.GetService("Workspace");
}
instantiateTree(tree).Parent = game.GetService("Workspace");
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment