forked from PolymerLabs/arcs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
schema2base.ts
128 lines (111 loc) · 4.49 KB
/
schema2base.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/**
* @license
* Copyright (c) 2019 Google Inc. All rights reserved.
* This code may only be used under the BSD style license found at
* http://polymer.github.io/LICENSE.txt
* Code distributed by Google as part of this project is also
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
*/
import fs from 'fs';
import path from 'path';
import minimist from 'minimist';
import {Manifest} from '../runtime/manifest.js';
import {Runtime} from '../runtime/runtime.js';
import {SchemaGraph, SchemaNode} from './schema2graph.js';
import {ParticleSpec} from '../runtime/particle-spec.js';
export type AddFieldOptions = Readonly<{
field: string;
typeChar: string;
isOptional?: boolean;
refClassName?: string;
isCollection?: boolean;
}>;
export interface ClassGenerator {
addField(opts: AddFieldOptions): void;
generate(schemaHash: string, fieldCount: number): string;
}
export abstract class Schema2Base {
scope: string;
constructor(readonly opts: minimist.ParsedArgs) {
Runtime.init('../..');
this.scope = this.opts.package || 'arcs.sdk';
}
async call() {
fs.mkdirSync(this.opts.outdir, {recursive: true});
for (const file of this.opts._) {
if (fs.existsSync(file)) {
await this.processFile(file);
} else {
throw new Error(`File not found: ${file}`);
}
}
}
private async processFile(src: string) {
const outName = this.opts.outfile || this.outputName(path.basename(src));
const outPath = path.join(this.opts.outdir, outName);
if (this.opts.update && fs.existsSync(outPath) && fs.statSync(outPath).mtimeMs > fs.statSync(src).mtimeMs) {
return;
}
const manifest = await Runtime.parseFile(src);
if (manifest.errors.some(e => e.severity !== 'warning')) {
return;
}
const classes = await this.processManifest(manifest);
if (classes.length === 0) {
console.warn(`Could not find any particle connections with schemas in '${src}'`);
return;
}
const outFile = fs.openSync(outPath, 'w');
fs.writeSync(outFile, this.fileHeader(outName));
for (const text of classes) {
fs.writeSync(outFile, text.replace(/ +\n/g, '\n'));
}
fs.writeSync(outFile, this.fileFooter());
fs.closeSync(outFile);
}
async processManifest(manifest: Manifest): Promise<string[]> {
// TODO: consider an option to generate one file per particle
const classes: string[] = [];
for (const particle of manifest.particles) {
const graph = new SchemaGraph(particle);
// Generate one class definition per node in the graph.
for (const node of graph.walk()) {
const generator = this.getClassGenerator(node);
const fields = Object.entries(node.schema.fields);
for (const [field, descriptor] of fields) {
if (descriptor.kind === 'schema-primitive') {
if (['Text', 'URL', 'Number', 'Boolean'].includes(descriptor.type)) {
generator.addField({field, typeChar: descriptor.type[0]});
} else {
throw new Error(`Schema type '${descriptor.type}' for field '${field}' is not supported`);
}
} else if (descriptor.kind === 'schema-reference') {
generator.addField({field, typeChar: 'R', refClassName: node.refs.get(field).name});
} else if (descriptor.kind === 'schema-collection' && descriptor.schema.kind === 'schema-reference') {
// TODO: support collections of references
} else if (descriptor.kind === 'schema-collection') {
const schema = descriptor.schema;
if (!['Text', 'URL', 'Number', 'Boolean'].includes(schema.type)) {
throw new Error(`Schema type '${schema.type}' for field '${field}' is not supported`);
}
generator.addField({field, typeChar: schema.type[0], isCollection: true});
} else {
throw new Error(`Schema kind '${descriptor.kind}' for field '${field}' is not supported`);
}
}
classes.push(generator.generate(await node.schema.hash(), fields.length));
}
classes.push(this.generateParticleClass(particle));
}
return classes;
}
upperFirst(s: string): string {
return s[0].toUpperCase() + s.slice(1);
}
outputName(baseName: string): string { return ''; }
fileHeader(outName: string): string { return ''; }
fileFooter(): string { return ''; }
abstract getClassGenerator(node: SchemaNode): ClassGenerator;
abstract generateParticleClass(particle: ParticleSpec): string;
}