Skip to content

Commit 0a7d15d

Browse files
committed
Add a new Vuetify generator
1 parent 28e231c commit 0a7d15d

39 files changed

+2314
-14
lines changed

src/generators.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import ReactGenerator from "./generators/ReactGenerator";
44
import ReactNativeGenerator from "./generators/ReactNativeGenerator";
55
import TypescriptInterfaceGenerator from "./generators/TypescriptInterfaceGenerator";
66
import VueGenerator from "./generators/VueGenerator";
7+
import VuetifyGenerator from "./generators/VuetifyGenerator";
78
import QuasarGenerator from "./generators/QuasarGenerator";
89

910
function wrap(cl) {
@@ -25,6 +26,8 @@ export default function generators(generator = "react") {
2526
return wrap(TypescriptInterfaceGenerator);
2627
case "vue":
2728
return wrap(VueGenerator);
29+
case "vuetify":
30+
return wrap(VuetifyGenerator);
2831
case "quasar":
2932
return wrap(QuasarGenerator);
3033
}

src/generators/BaseGenerator.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ export default class {
4545
}
4646

4747
createFile(template, dest, context = {}, warn = true) {
48+
if (undefined === this.templates[template]) {
49+
console.log(
50+
`The template ${template} does not exists in the registered templates.`
51+
);
52+
53+
return;
54+
}
55+
4856
if (!fs.existsSync(dest)) {
4957
fs.writeFileSync(dest, this.templates[template](context));
5058

src/generators/VueBaseGenerator.js

Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
import BaseGenerator from "./BaseGenerator";
2+
import handlebars from "handlebars";
3+
import hbh_comparison from "handlebars-helpers/lib/comparison";
4+
import hbh_array from "handlebars-helpers/lib/array";
5+
import hbh_string from "handlebars-helpers/lib/string";
6+
import { sprintf } from "sprintf-js";
7+
8+
export default class extends BaseGenerator {
9+
constructor(params) {
10+
super(params);
11+
12+
this.registerTemplates("vue-common/", [
13+
// error
14+
"error/SubmissionError.js",
15+
16+
// mixins
17+
"mixins/CreateMixin.js",
18+
"mixins/ListMixin.js",
19+
"mixins/NotificationMixin.js",
20+
"mixins/ShowMixin.js",
21+
"mixins/UpdateMixin.js",
22+
23+
// services
24+
"services/api.js",
25+
"services/foo.js",
26+
27+
// modules
28+
"store/modules/crud.js",
29+
"store/modules/notifications.js",
30+
31+
// utils
32+
"utils/dates.js",
33+
"utils/fetch.js",
34+
"utils/hydra.js",
35+
36+
// validators
37+
"validators/date.js"
38+
]);
39+
40+
handlebars.registerHelper("compare", hbh_comparison.compare);
41+
handlebars.registerHelper("ifEven", hbh_comparison.ifEven);
42+
handlebars.registerHelper("ifOdd", hbh_comparison.ifOdd);
43+
handlebars.registerHelper("isArray", hbh_array.isArray);
44+
handlebars.registerHelper("inArray", hbh_array.inArray);
45+
handlebars.registerHelper("forEach", hbh_array.forEach);
46+
handlebars.registerHelper("downcase", hbh_string.downcase);
47+
48+
this.registerSwitchHelper();
49+
}
50+
51+
registerSwitchHelper() {
52+
/*
53+
https://github.com/wycats/handlebars.js/issues/927#issuecomment-318640459
54+
55+
{{#switch state}}
56+
{{#case "page1" "page2"}}page 1 or 2{{/case}}
57+
{{#case "page3"}}page3{{/case}}
58+
{{#case "page4"}}page4{{/case}}
59+
{{#case "page5"}}
60+
{{#switch s}}
61+
{{#case "3"}}s = 3{{/case}}
62+
{{#case "2"}}s = 2{{/case}}
63+
{{#case "1"}}s = 1{{/case}}
64+
{{#default}}unknown{{/default}}
65+
{{/switch}}
66+
{{/case}}
67+
{{#default}}page0{{/default}}
68+
{{/switch}}
69+
*/
70+
handlebars.__switch_stack__ = [];
71+
72+
handlebars.registerHelper("switch", function(value, options) {
73+
handlebars.__switch_stack__.push({
74+
switch_match: false,
75+
switch_value: value
76+
});
77+
let html = options.fn(this);
78+
handlebars.__switch_stack__.pop();
79+
return html;
80+
});
81+
handlebars.registerHelper("case", function(value, options) {
82+
let args = Array.from(arguments);
83+
options = args.pop();
84+
let caseValues = args;
85+
let stack =
86+
handlebars.__switch_stack__[handlebars.__switch_stack__.length - 1];
87+
88+
if (stack.switch_match || caseValues.indexOf(stack.switch_value) === -1) {
89+
return "";
90+
} else {
91+
stack.switch_match = true;
92+
return options.fn(this);
93+
}
94+
});
95+
handlebars.registerHelper("default", function(options) {
96+
let stack =
97+
handlebars.__switch_stack__[handlebars.__switch_stack__.length - 1];
98+
if (!stack.switch_match) {
99+
return options.fn(this);
100+
}
101+
});
102+
}
103+
104+
getContextForResource(resource, params) {
105+
const lc = resource.title.toLowerCase();
106+
const titleUcFirst =
107+
resource.title.charAt(0).toUpperCase() + resource.title.slice(1);
108+
109+
const formFields = this.buildFields(resource.writableFields);
110+
111+
const dateTypes = ["time", "date", "dateTime"];
112+
const formContainsDate = formFields.some(e => dateTypes.includes(e.type));
113+
114+
const fields = this.buildFields(resource.readableFields);
115+
const listContainsDate = fields.some(e => dateTypes.includes(e.type));
116+
117+
const parameters = [];
118+
params.forEach(p => {
119+
const param = fields.find(field => field.name === p.variable);
120+
if (!param) {
121+
p.name = p.variable;
122+
parameters.push(p);
123+
} else {
124+
param.multiple = p.multiple;
125+
parameters.push(param);
126+
}
127+
});
128+
129+
const paramsHaveRefs = parameters.some(
130+
e => e.type === "text" && e.reference
131+
);
132+
133+
const labels = this.commonLabelTexts();
134+
135+
return {
136+
title: resource.title,
137+
name: resource.name,
138+
lc,
139+
uc: resource.title.toUpperCase(),
140+
fields,
141+
dateTypes,
142+
listContainsDate,
143+
paramsHaveRefs,
144+
parameters,
145+
formFields,
146+
formContainsDate,
147+
hydraPrefix: this.hydraPrefix,
148+
titleUcFirst,
149+
labels
150+
};
151+
}
152+
153+
generate(api, resource, dir) {
154+
return resource.getParameters().then(params => {
155+
params = params.map(param => ({
156+
...param,
157+
...this.getHtmlInputTypeFromField(param)
158+
}));
159+
160+
params = this.cleanupParams(params);
161+
162+
this.generateFiles(api, resource, dir, params);
163+
});
164+
}
165+
166+
// eslint-disable-next-line no-unused-vars
167+
generateFiles(api, resource, dir, params) {
168+
// Create directories
169+
// These directories may already exist
170+
[
171+
`${dir}/config`,
172+
`${dir}/error`,
173+
`${dir}/mixins`,
174+
`${dir}/router`,
175+
`${dir}/services`,
176+
`${dir}/store/modules`,
177+
`${dir}/utils`,
178+
`${dir}/validators`
179+
].forEach(dir => this.createDir(dir, false));
180+
181+
// error
182+
this.createFile(
183+
"error/SubmissionError.js",
184+
`${dir}/error/SubmissionError.js`,
185+
{},
186+
false
187+
);
188+
189+
// mixins
190+
[
191+
"mixins/Create%s.js",
192+
"mixins/List%s.js",
193+
"mixins/Notification%s.js",
194+
"mixins/Show%s.js",
195+
"mixins/Update%s.js"
196+
].forEach(pattern =>
197+
this.createFile(
198+
sprintf(`${pattern}`, "Mixin"),
199+
sprintf(`${dir}/${pattern}`, "Mixin"),
200+
{},
201+
false
202+
)
203+
);
204+
205+
// stores
206+
["crud.js", "notifications.js"].forEach(file =>
207+
this.createFile(
208+
`store/modules/${file}`,
209+
`${dir}/store/modules/${file}`,
210+
{ hydraPrefix: this.hydraPrefix },
211+
false
212+
)
213+
);
214+
215+
// services
216+
this.createFile("services/api.js", `${dir}/services/api.js`, {}, false);
217+
this.createFileFromPattern(
218+
"services/%s.js",
219+
dir,
220+
resource.title.toLowerCase(),
221+
{ name: resource.name }
222+
);
223+
224+
// validators
225+
this.createFile(
226+
"validators/date.js",
227+
`${dir}/validators/date.js`,
228+
{ hydraPrefix: this.hydraPrefix },
229+
false
230+
);
231+
232+
// utils
233+
["dates.js", "fetch.js", "hydra.js"].forEach(file =>
234+
this.createFile(`utils/${file}`, `${dir}/utils/${file}`, {}, false)
235+
);
236+
237+
this.createEntrypoint(api.entrypoint, `${dir}/config/entrypoint.js`);
238+
}
239+
240+
cleanupParams(params) {
241+
const stats = {};
242+
const result = [];
243+
244+
params.forEach(p => {
245+
let key = p.variable.endsWith("[]")
246+
? p.variable.slice(0, -2)
247+
: p.variable;
248+
if (!stats[key]) {
249+
stats[key] = 0;
250+
}
251+
stats[key] += 1;
252+
});
253+
254+
params.forEach(p => {
255+
if (p.variable.endsWith("[exists]")) {
256+
return; // removed for the moment, it can help to add null option to select
257+
}
258+
if (p.variable.startsWith("order[")) {
259+
return; // removed for the moment, it can help to sorting data
260+
}
261+
if (!stats[p.variable] && p.variable.endsWith("[]")) {
262+
if (stats[p.variable.slice(0, -2)] === 1) {
263+
result.push(p);
264+
}
265+
} else {
266+
if (stats[p.variable] === 2) {
267+
p.multiple = true;
268+
}
269+
result.push(p);
270+
}
271+
});
272+
273+
return result;
274+
}
275+
276+
contextLabelTexts(formFields, fields) {
277+
let texts = [];
278+
formFields.forEach(x => texts.push(x.name)); // forms
279+
fields.forEach(x => texts.push(x.name)); // for show, too
280+
return [...new Set(texts)];
281+
}
282+
283+
commonLabelTexts() {
284+
return {
285+
submit: "Submit",
286+
reset: "Reset",
287+
delete: "Delete",
288+
confirmDelete: "Are you sure you want to delete this item?",
289+
noresults: "No results",
290+
close: "Close",
291+
cancel: "Cancel",
292+
updated: "Updated",
293+
field: "Field",
294+
value: "Value",
295+
filters: "Filters",
296+
filter: "Filter",
297+
unavail: "Data unavailable",
298+
loading: "Loading...",
299+
deleted: "Deleted",
300+
numValidation: "Please, insert a value bigger than zero!",
301+
stringValidation: "Please type something",
302+
required: "Field is required",
303+
recPerPage: "Records per page:"
304+
};
305+
}
306+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import Api from "@api-platform/api-doc-parser/lib/Api";
2+
import Resource from "@api-platform/api-doc-parser/lib/Resource";
3+
import Field from "@api-platform/api-doc-parser/lib/Field";
4+
import fs from "fs";
5+
import tmp from "tmp";
6+
import VueBaseGenerator from "./VueBaseGenerator";
7+
8+
test("Test VueBaseGenerator", () => {
9+
const generator = new VueBaseGenerator({
10+
hydraPrefix: "hydra:",
11+
templateDirectory: `${__dirname}/../../templates`
12+
});
13+
const tmpobj = tmp.dirSync({ unsafeCleanup: true });
14+
15+
const fields = [
16+
new Field("bar", {
17+
id: "http://schema.org/url",
18+
range: "http://www.w3.org/2001/XMLSchema#string",
19+
reference: null,
20+
required: true,
21+
description: "An URL"
22+
})
23+
];
24+
const resource = new Resource("abc", "http://example.com/foos", {
25+
id: "foo",
26+
title: "Foo",
27+
readableFields: fields,
28+
writableFields: fields,
29+
getParameters: function getParameters() {
30+
return Promise.resolve([]);
31+
}
32+
});
33+
const api = new Api("http://example.com", {
34+
entrypoint: "http://example.com:8080",
35+
title: "My API",
36+
resources: [resource]
37+
});
38+
generator.generate(api, resource, tmpobj.name).then(() => {
39+
expect(fs.existsSync(tmpobj.name + "/mixins/CreateMixin.js")).toBe(true);
40+
expect(fs.existsSync(tmpobj.name + "/mixins/ListMixin.js")).toBe(true);
41+
expect(fs.existsSync(tmpobj.name + "/mixins/NotificationMixin.js")).toBe(
42+
true
43+
);
44+
expect(fs.existsSync(tmpobj.name + "/mixins/ShowMixin.js")).toBe(true);
45+
expect(fs.existsSync(tmpobj.name + "/mixins/UpdateMixin.js")).toBe(true);
46+
47+
expect(fs.existsSync(tmpobj.name + "/error/SubmissionError.js")).toBe(true);
48+
49+
expect(fs.existsSync(tmpobj.name + "/store/modules/crud.js")).toBe(true);
50+
expect(fs.existsSync(tmpobj.name + "/store/modules/notifications.js")).toBe(
51+
true
52+
);
53+
54+
expect(fs.existsSync(tmpobj.name + "/utils/dates.js")).toBe(true);
55+
expect(fs.existsSync(tmpobj.name + "/utils/fetch.js")).toBe(true);
56+
expect(fs.existsSync(tmpobj.name + "/utils/hydra.js")).toBe(true);
57+
58+
tmpobj.removeCallback();
59+
});
60+
});

0 commit comments

Comments
 (0)