Skip to content

Commit cd16a24

Browse files
committed
Implemented some code generation based on spec of inputs
1 parent 3824296 commit cd16a24

5 files changed

Lines changed: 96 additions & 36 deletions

File tree

solves/base-template/src/components/App.tsx renamed to solves/base-template/src/components/App.tsx.ejs

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -88,30 +88,16 @@ export class App extends React.Component<{}, State> {
8888
};
8989

9090
return <div id="app">
91-
<div className="text-input-wrapper">
92-
<LineHighlighterTextbox
93-
label="Colours"
94-
initialValue={this.state.inputColours}
95-
errorLineNumbers={this.state.invalidInputLines.colours}
96-
onChange={setColours}
97-
/>
98-
</div>
99-
<div className="text-input-wrapper">
100-
<LineHighlighterTextbox
101-
label="Vertices"
102-
initialValue={this.state.inputVertices}
103-
errorLineNumbers={this.state.invalidInputLines.vertices}
104-
onChange={setVertices}
105-
/>
106-
</div>
107-
<div className="text-input-wrapper">
108-
<LineHighlighterTextbox
109-
label="Edges"
110-
initialValue={this.state.inputEdges}
111-
errorLineNumbers={this.state.invalidInputLines.edges}
112-
onChange={setEdges}
113-
/>
114-
</div>
91+
<%-
92+
Object.entries(input).map(([k, v]) => {
93+
const x = {};
94+
x.title = v.title;
95+
x.stateValue = "input" + upperCaseFirst(k);
96+
x.stateValidation = k;
97+
x.onChange = "set" + upperCaseFirst(k);
98+
return ht.jsxParts(x);
99+
}).join("")
100+
%>
115101
<div className="output-table-wrapper">
116102
<ResultDisplay
117103
clingoResult={this.state.outputResult}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<div className="text-input-wrapper">
2+
<LineHighlighterTextbox
3+
label="<%- title %>"
4+
initialValue={this.state.<%- stateValue %>}
5+
errorLineNumbers={this.state.invalidInputLines.<%- stateValidation %>}
6+
onChange={<%- onChange %>}
7+
/>
8+
</div>

solves/src/code-generator/base-template.ts

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,23 @@ import path from "path";
99

1010
import ejs from "ejs";
1111

12+
import {type SpecValues} from "../spec-parse";
13+
import templatesHelperAPI from "./templates-helper-api"
1214
import * as utils from "../utils";
1315

1416

1517
const EJS_FILE_EXTENSION = ".ejs";
18+
const HELPERS_EXTENSION = ".ejshelpers";
1619
const PACKAGES_DIR_NAME = "packages";
1720
const CLINGO_WRAPPER_PATH = "../clingo-wrapper";
1821

19-
export interface Substitutions {
20-
name: string;
21-
encoding: string;
22-
constraints: string;
22+
const reExtension = /.[a-zA-Z0-9]+$/;
23+
24+
type HelperTemplateFn = (subs: {[key: string]: string}) => string;
25+
type HelperTemplates = Map<string, {[key: string]: HelperTemplateFn}>;
26+
27+
export interface Substitutions extends SpecValues {
28+
// Nothing for now
2329
}
2430

2531
/*
@@ -32,7 +38,7 @@ export function copyDirAndApplyTemplate(
3238
dst: string,
3339
substitutions: Substitutions,
3440
) {
35-
const retypedSubstitutions: {[key: string]: string} = Object.fromEntries(
41+
const retypedSubstitutions: {[key: string]: any} = Object.fromEntries(
3642
Object.entries(substitutions),
3743
);
3844
_copyDirAndApplyTemplate(src, dst, retypedSubstitutions);
@@ -45,7 +51,7 @@ export function copyDirAndApplyTemplate(
4551
function _copyDirAndApplyTemplate(
4652
src: string,
4753
dst: string,
48-
substitutions: {[key: string]: string},
54+
substitutions: {[key: string]: any},
4955
) {
5056
if (!utils.lstatIfExist(src)?.isDirectory()) {
5157
throw new Error("Source isn't a directory.");
@@ -55,21 +61,60 @@ function _copyDirAndApplyTemplate(
5561
}
5662
fs.mkdirSync(dst);
5763

58-
for (const direntObj of fs.readdirSync(src, {withFileTypes: true})) {
64+
// We first process helper templates
65+
const direntObjs = fs.readdirSync(src, {withFileTypes: true});
66+
const helperTemplates: HelperTemplates = new Map();
67+
for (const direntObj of direntObjs) {
68+
const newSRC = path.join(src, direntObj.name);
69+
if (direntObj.name.endsWith(HELPERS_EXTENSION)) {
70+
helperTemplates.set(path.basename(newSRC), readHelpersDir(newSRC));
71+
}
72+
}
73+
74+
for (const direntObj of direntObjs) {
5975
const newSRC = path.join(src, direntObj.name);
6076
const newDST = path.join(dst, direntObj.name);
77+
if (helperTemplates.has(path.basename(newSRC))) continue;
78+
6179
if (direntObj.isDirectory()) {
6280
_copyDirAndApplyTemplate(newSRC, newDST, substitutions);
6381
} else if (direntObj.name.endsWith(EJS_FILE_EXTENSION)) {
6482
const fileData = fs.readFileSync(newSRC).toString();
6583
const modifiedDST = newDST.slice(0, -EJS_FILE_EXTENSION.length);
66-
fs.writeFileSync(modifiedDST, ejs.render(fileData, substitutions));
84+
const nameWithoutExt = path
85+
.basename(newSRC)
86+
.slice(0, -EJS_FILE_EXTENSION.length);
87+
88+
const modifiedSubstitutions = {...substitutions, ...templatesHelperAPI};
89+
const ht = helperTemplates.get(nameWithoutExt + HELPERS_EXTENSION);
90+
if (ht !== undefined) modifiedSubstitutions["ht"] = ht;
91+
console.log(modifiedSubstitutions);
92+
try {
93+
fs.writeFileSync(modifiedDST, ejs.render(fileData, modifiedSubstitutions));
94+
} catch (err) {
95+
console.error(`Error when rendering template '${newSRC}'.`);
96+
throw err;
97+
}
6798
} else {
6899
fs.copyFileSync(newSRC, newDST);
69100
}
70101
}
71102
}
72103

104+
105+
function readHelpersDir(basePath: string): {[key: string]: HelperTemplateFn} {
106+
const ret: {[key: string]: HelperTemplateFn} = {};
107+
for (const direntObj of fs.readdirSync(basePath, {withFileTypes: true})) {
108+
const filePath = path.join(basePath, direntObj.name);
109+
const fileData = fs.readFileSync(filePath).toString();
110+
const key = direntObj.name
111+
.slice(0, -EJS_FILE_EXTENSION.length)
112+
.replace(reExtension, ""); // We ignore the extra extension!
113+
ret[key] = subs => ejs.render(fileData, {...subs, ...templatesHelperAPI});
114+
}
115+
return ret;
116+
}
117+
73118
/*
74119
* Copies helper workspaces into the new project.
75120
*/

solves/src/code-generator/index.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ const BASE_TEMPLATE_PATH = "./base-template";
1414

1515
export function generateSource(specValues: SpecValues, cliArgs: ArgValues) {
1616
copyDirAndApplyTemplate(BASE_TEMPLATE_PATH, cliArgs.sourceOutputDirPath, {
17-
name: specValues.name,
18-
encoding: specValues.encoding,
19-
constraints: specValues.constraints,
17+
...specValues,
2018
});
2119
// TODO: Sanitize to prevent command injection
22-
execute(`cd ${cliArgs.sourceOutputDirPath} && yarn set version stable && yarn install && yarn build --output-path ${cliArgs.appOutputDirPath}`);
20+
execute(
21+
`cd ${cliArgs.sourceOutputDirPath}`
22+
+ " && yarn set version stable && yarn install"
23+
+ ` && yarn build --output-path ${cliArgs.appOutputDirPath}`
24+
);
2325
}
2426

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Filename: templates-helper-api.ts
3+
* Author: simshadows <[email protected]>
4+
* License: GNU Affero General Public License v3 (AGPL-3.0)
5+
*/
6+
7+
const helperAPI: {[key: string]: any} = {
8+
upperCaseFirst: (s: string) => {
9+
// Some type checks since this actually runs in an EJS environment
10+
if (typeof s !== "string") {
11+
throw new Error(`upperCaseFirst() expected a string. Instead got type ${typeof s}.`);
12+
}
13+
14+
return s.slice(0,1).toUpperCase() + s.slice(1)
15+
},
16+
};
17+
18+
export default helperAPI;
19+

0 commit comments

Comments
 (0)