parse-factorio-model.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. import fs from "fs";
  2. import Handlebars from "handlebars";
  3. import type * as M from "./lua-api.models.js";
  4. import { FULL_API_TEMPLATE } from "./template.ts";
  5. import path from "path";
  6. interface Data {
  7. app: string;
  8. appVersion: string;
  9. apiVersion: number;
  10. stage: string;
  11. interfaces: Interface[];
  12. types: DataType[];
  13. }
  14. interface Interface {
  15. name: string;
  16. description: string;
  17. export: boolean;
  18. extends?: string;
  19. typeName?: string;
  20. props: Property[];
  21. isTypeFunction?: string;
  22. }
  23. interface Property {
  24. name: string;
  25. description: string;
  26. optional: boolean;
  27. type: string;
  28. }
  29. interface DataType {
  30. name: string;
  31. description: string;
  32. value: string;
  33. }
  34. function parseType(type: M.DataType, structName?: string): string {
  35. if (typeof type === "string") {
  36. // Handle known built-in types
  37. switch (type) {
  38. case "bool":
  39. return "boolean";
  40. case "double":
  41. case "float":
  42. case "int8":
  43. case "int16":
  44. case "int32":
  45. case "uint8":
  46. case "uint16":
  47. case "uint32":
  48. case "uint64":
  49. return "number";
  50. case "string":
  51. return "string";
  52. case "DataExtendMethod":
  53. return "() => void";
  54. default:
  55. return type;
  56. }
  57. }
  58. switch (type.complex_type) {
  59. case "array":
  60. return `${parseType(type.value, structName)}[]`;
  61. case "dictionary":
  62. return `Record<${parseType(type.key, structName)}, ${parseType(type.value, structName)}>`;
  63. case "literal": {
  64. if (typeof type.value === "string") {
  65. return `'${type.value}'`;
  66. } else {
  67. return type.value.toString();
  68. }
  69. }
  70. case "struct":
  71. if (structName == null)
  72. throw new Error("Unexpected struct: use properties on parent or pass a struct name to use");
  73. return structName;
  74. case "tuple":
  75. return `[${type.values.map((v) => parseType(v, structName)).join(", ")}]`;
  76. case "type":
  77. return parseType(type.value, structName);
  78. case "union":
  79. return `(${type.options.map((o) => parseType(o, structName)).join(" | ")})`;
  80. }
  81. }
  82. function parseProperty(prop: M.Property): Property {
  83. return {
  84. name: prop.name,
  85. description: prop.description,
  86. optional: prop.optional,
  87. type: parseType(prop.type),
  88. };
  89. }
  90. function getTypePropertyValue(props: M.Property[]): string | undefined {
  91. const typeProp = props.find((p) => p.name === "type");
  92. if (
  93. typeProp &&
  94. typeof typeProp.type !== "string" &&
  95. typeProp.type.complex_type === "literal" &&
  96. typeof typeProp.type.value === "string"
  97. ) {
  98. return typeProp.type.value;
  99. }
  100. return undefined;
  101. }
  102. export function parsePrototypeApi(api: M.PrototypeApi): Data {
  103. const data: Data = {
  104. app: api.application,
  105. appVersion: api.application_version,
  106. apiVersion: api.api_version,
  107. stage: api.stage,
  108. interfaces: [],
  109. types: [],
  110. };
  111. api.prototypes.forEach((p) => {
  112. const int: Interface = {
  113. name: p.name,
  114. description: p.description,
  115. export: p.parent == null,
  116. extends: p.parent,
  117. props: p.properties.map((p) => parseProperty(p)),
  118. };
  119. const typePropValue = getTypePropertyValue(p.properties);
  120. if (typePropValue != null) {
  121. int.isTypeFunction = typePropValue;
  122. } else if (p.typename) {
  123. int.typeName = p.typename;
  124. int.isTypeFunction = p.typename;
  125. }
  126. data.interfaces.push(int);
  127. });
  128. api.types.forEach((t) => {
  129. if (typeof t.type === "string") {
  130. if (t.type !== "builtin") {
  131. const dataType: DataType = {
  132. name: t.name,
  133. description: t.description,
  134. value: parseType(t.type),
  135. };
  136. data.types.push(dataType);
  137. }
  138. } else if (t.type.complex_type === "struct") {
  139. const int: Interface = {
  140. name: t.name,
  141. description: t.description,
  142. export: t.parent == null,
  143. extends: t.parent,
  144. props: [],
  145. };
  146. if (t.properties) {
  147. int.props = t.properties.map((p) => parseProperty(p));
  148. int.isTypeFunction = getTypePropertyValue(t.properties);
  149. }
  150. data.interfaces.push(int);
  151. } else {
  152. let structName: string | undefined;
  153. if (t.properties) {
  154. structName = `_${t.name}`;
  155. const int: Interface = {
  156. name: structName,
  157. description: t.description,
  158. export: false,
  159. extends: t.parent,
  160. props: t.properties.map((p) => parseProperty(p)),
  161. };
  162. data.interfaces.push(int);
  163. }
  164. const dataType: DataType = {
  165. name: t.name,
  166. description: t.description,
  167. value: parseType(t.type, structName),
  168. };
  169. data.types.push(dataType);
  170. }
  171. });
  172. return data;
  173. }
  174. export const generateDataModel = async function (outputFolder: string, api: M.PrototypeApi): Promise<void> {
  175. const data = parsePrototypeApi(api);
  176. const modelsTemplate = Handlebars.compile(FULL_API_TEMPLATE);
  177. const result = modelsTemplate(data);
  178. fs.writeFileSync(path.join(outputFolder, "models.ts"), result);
  179. console.log(`Generated ${api.prototypes.length.toString()} models`);
  180. };