Skip to content
This repository has been archived by the owner on Feb 27, 2024. It is now read-only.

Commit

Permalink
Refactor GMS2ProjectLoader
Browse files Browse the repository at this point in the history
  • Loading branch information
jhm-ciberman committed Mar 2, 2018
1 parent d2bb6c8 commit 710c953
Show file tree
Hide file tree
Showing 12 changed files with 184 additions and 206 deletions.
4 changes: 2 additions & 2 deletions src/cli/CliGenerateFacade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ export default class CliGenerateFacade implements ICliGenerateFacade {

this._reporter.info("Loading Project...");

const loader = new ProjectLoader(projectPath);
const project = await loader.load();
const loader = new ProjectLoader();
const project = await loader.load(projectPath);

this._reporter.info("Loading project configuration...");

Expand Down
3 changes: 0 additions & 3 deletions src/config/OutputConfig.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import { injectable } from "inversify";

import IOutputConfig from "./interfaces/IOutputConfig";

/**
* This class has all the configuration for the DocsGM Output
*/
@injectable()
export default class OutputConfig implements IOutputConfig {
/**
* The design name. If empty, it will use the first design in the designs list.
Expand Down
35 changes: 11 additions & 24 deletions src/gm_project/ProjectLoader.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,24 @@
import * as globby from "globby";
import * as path from "path";

import GMS1ProjectFactory from "../gm_project/gms1/GMS1ProjectFactory";
import GMS2ProjectFactory from "../gm_project/gms2/GMS2ProjectFactory";
import IGMProject from "../gm_project/interfaces/IGMProject";
import IGMProjectFactory from "../gm_project/interfaces/IGMProjectFactory";
import GMS1ProjectLoader from "./gms1/GMS1ProjectLoader";
import GMS2ProjectLoader from "./gms2/GMS2ProjectLoader";
import IGMProjectLoader from "./interfaces/IGMProjectLoader";

/**
* This Factory class loads a GMS1 or GMS2 project and returns a GMProject object
*/
export default class ProjectLoader {

/**
* The project path
*/
private readonly _gmProjectPath: string;

/**
* Creates a new Project loader
* @param gmProjectPath The path of the project to load
*/
public constructor(gmProjectPath: string) {
this._gmProjectPath = gmProjectPath;
}
export default class ProjectLoader implements IGMProjectLoader {

/**
* Loads a specified GMS1 or GMS2 Project
* @param GMProjectPath The project path to load
* @param gmProjectPath The project path to load
* @return A promise with the loaded project
*/
public async load(): Promise<IGMProject> {
public async load(gmProjectPath: string): Promise<IGMProject> {

const files = await globby(this._gmProjectPath + "/*.{yyp,gmx}");
const files = await globby(gmProjectPath + "/*.{yyp,gmx}");

if (files.length === 0) {
throw new Error("Unrecognized GM project. No *.yyp or *.gmx file found");
Expand All @@ -41,12 +28,12 @@ export default class ProjectLoader {

const ext = extArr[extArr.length - 1];

let factory: IGMProjectFactory;
let loader: IGMProjectLoader;
if (ext === "gmx") {
factory = new GMS1ProjectFactory(files[0]);
loader = new GMS1ProjectLoader();
} else {
factory = new GMS2ProjectFactory(files[0]);
loader = new GMS2ProjectLoader();
}
return factory.load();
return loader.load(files[0]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,52 +5,35 @@ import * as xml2js from "xml2js";
import GMFolder from "../GMFolder";
import GMProject from "../GMProject";
import IGMProject from "../interfaces/IGMProject";
import IGMProjectFactory from "../interfaces/IGMProjectFactory";
import IGMProjectLoader from "../interfaces/IGMProjectLoader";
import GMS1Script from "./GMS1Script";
import { IGMS1DescriptorFolder, IGMS1DescriptorRoot } from "./IGMS1Descriptor";

/**
* Loads a GMS1 project file and creates a GMS1Project
*/
export default class GMS1ProjectFactory implements IGMProjectFactory {

/**
* The project file
*/
private readonly _file: string;

/**
* The project to create
*/
private readonly _project: IGMProject;

/**
* Creates a new GMS1Project Factory
* @param file The project file
*/
constructor(file: string) {
this._file = file;
this._project = new GMProject(path.dirname(this._file));
}
export default class GMS1ProjectLoader implements IGMProjectLoader {

/**
* Loads the specified GMS1 project
* @param file The file path of the project to load
* @returns A Promise with the GMS1Project
*/
public async load(): Promise<IGMProject> {
const str = await fse.readFile(this._file, "utf8");
public async load(file: string): Promise<IGMProject> {
const project = new GMProject(path.dirname(file));

const str = await fse.readFile(file, "utf8");
// "assets" is the root XML node of the document, but we call Root at the content of that node
const data: IGMS1DescriptorRoot = await this._xmlParse(str);

const assets = data.assets;
// Creates the root folder
if (assets.scripts) {
const scriptsFolder = this._createFolder(assets.scripts[0]);
this._project.addChild(scriptsFolder);
project.addChild(scriptsFolder);
}

return this._project;
return project;
}

/**
Expand Down
159 changes: 60 additions & 99 deletions src/gm_project/gms2/GMS2ProjectFactory.ts
Original file line number Diff line number Diff line change
@@ -1,153 +1,114 @@
import * as fse from "fs-extra";
import * as path from "path";

import GMFolder from "../GMFolder";
import GMProject from "../GMProject";
import GMResource from "../GMResource";
import IGMFolder from "../interfaces/IGMFolder";
import IGMProject from "../interfaces/IGMProject";
import IGMProjectFactory from "../interfaces/IGMProjectFactory";
import IGMResource from "../interfaces/IGMResource";
import GMS2ResourceType from "./GMS2ResourceType";
import GMS2Script from "./GMS2Script";
import { IFolder, IProject, IResource, IResourceInfo, IScript } from "./IGMS2Descriptor";

/**
* Creates a new GameMakerStudio 2 Project Factory that loads a project
* file and creates a new GMProject
* Factory class to create GMS2Project instances
*/
export default class GMS2ProjectFactory implements IGMProjectFactory {

/**
* A map with each key is the GMS2 parent folder and each value is an
* array with the keys of the childrens of that folder.
*/
private _folderChildsKeys: Map<IGMFolder, string[]> = new Map();
export default class GMS2ProjectFactory {

/**
* A map with the resources by key.
* The created GMProject
*/
private _resourcesByKey: Map<string, GMResource> = new Map();
private _project: IGMProject;

/**
* The project file
* A map with each GMFolder, and an array with the children key ids of each folder
*/
private readonly _file: string;
private _folderChildrenKeys: Map<IGMFolder, string[]> = new Map();

/**
* The GMS2Project to create
* A map with the key ids and the associated resource
*/
private readonly _project: IGMProject;
private _resourcesByKey: Map<string, GMResource> = new Map();

/**
* Creates GMS2ProjectFactory
* @param file The project file
* Creates an instance of GMS2ProjectFactory.
* @param {string} gmProjectFolder The project folder
* @memberof GMS2ProjectFactory
*/
constructor(file: string) {
this._file = file;
this._project = new GMProject(path.dirname(this._file));
public constructor(gmProjectFolder: string) {
this._project = new GMProject(gmProjectFolder);
}

/**
* Loads the specified GMS2 project
* @param file The file path of the project to load
* @returns A Promise with the created GMS2Project
* Builds the GMS2Project instance and returns it
*/
public async load(): Promise<IGMProject> {
const str = await fse.readFile(this._file, "utf8");
const data: IProject = JSON.parse(str);

for (const item of data.resources) {
await this._addResource(item.Key, item.Value);
}
public build() {
for (const folder of this._project.children) {
this._buildSubTree(folder);
this._buildSubtree(folder);
}
return this._project;
}

/**
* Build subtre for the specified folder
* @param folder The folder to find the childrens
* Adds a new GMS2Folder to the project
*
* @param {string} key The resource key id
* @param {string} folderName The folder name
* @param {string} localizedFolderName The localizedFolderName
* @param {string[]} children An array with the children keys id of the folder
* @memberof GMS2ProjectFactory
*/
private _buildSubTree(folder: IGMFolder) {
const children = this._folderChildsKeys.get(folder) as string[];
for (const childKey of children) {
const child = this._resourcesByKey.get(childKey);
if (child) {
folder.addChild(child);
if (this._isFolder(child)) {
this._buildSubTree(child);
}
}
public addFolder(key: string, folderName: string, localizedFolderName: string, children: string[]): void {
const topLevelName = localizedFolderName.split("ResourceTree_").join("");
const folder = new GMFolder(folderName);
this._folderChildrenKeys.set(folder, children);
if (topLevelName !== "") {
this._project.addChild(folder);
}
this._resourcesByKey.set(key, folder);
}

/**
* Determines if the resource is a folder (ducktyping)
* @param res The resource
* Adds a new GMS2Script
*
* @param {string} key The resource key id
* @param {string} name The script name
* @param {boolean} isCompatibility Is a compatibility script?
* @memberof GMS2ProjectFactory
*/
private _isFolder(res: IGMResource): res is IGMFolder {
return ("addChild" in res);
public addScript(key: string, name: string, isCompatibility: boolean): void {
const script = new GMS2Script(name, isCompatibility);
this._resourcesByKey.set(key, script);
}

/**
* Adds and loads a resource
* @param key Resource Key
* @param resource ResourceInfo
* Adds a new Resource to the GMS2Project
* @param key The resource key id
* @param name The resource name
*/
private async _addResource(key: string, resource: IResourceInfo) {
const data = await this._loadResourceData(resource.resourcePath);
const res = this._createFromData(data);
public addResource(key: string, name: string) {
const res = new GMResource(name);
this._resourcesByKey.set(key, res);
}

/**
* Creates a GMS2Resource from a YOYO model data
* @param modelData The YOYO model data to create the GMS2Resource from
* Build subtree for the specified folder
* @param folder The folder to find the children
*/
private _createFromData(modelData: IResource): GMResource {
switch (modelData.modelName) {
case GMS2ResourceType.GMFolder:
return this._createFolder(modelData as IFolder);
case GMS2ResourceType.GMScript:
return this._createScript(modelData as IScript);
default:
return new GMResource(modelData.name);
}
}

/**
* Load a resource JSON data from a file
* @param filename The relative path to load the data from
*/
private async _loadResourceData(filename: string): Promise<IResource> {
const file = path.resolve(this._project.path, filename);
const str = await fse.readFile(file, "utf8");
return JSON.parse(str);
}

/**
* Creates a new GMS2Folder
* @param data The folder data
*/
private _createFolder(data: IFolder): GMFolder {
const topLevelName = data.localisedFolderName.split("ResourceTree_").join("");
const folder = new GMFolder(data.folderName);
this._folderChildsKeys.set(folder, data.children);
if (topLevelName !== "") {
this._project.addChild(folder);
private _buildSubtree(folder: IGMFolder) {
const children = this._folderChildrenKeys.get(folder) as string[];
for (const childKey of children) {
const child = this._resourcesByKey.get(childKey);
if (child) {
folder.addChild(child);
if (this._isFolder(child)) {
this._buildSubtree(child);
}
}
}
return folder;
}

/**
* Creates a new GMS2Script
* @param data The Script data
* Determines if the resource is a folder (ducktyping)
* @param res The resource
*/
private _createScript(data: IScript): GMS2Script {
const script = new GMS2Script(data.name);
script.isCompatibility = data.IsCompatibility;
return script;
private _isFolder(res: IGMResource): res is IGMFolder {
return ("addChild" in res);
}
}
Loading

0 comments on commit 710c953

Please sign in to comment.