Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- const fs = require('fs');
- const methods = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH', 'TRACE', 'CONNECT'];
- const jtt = {
- namespaceStore: {},
- typeData: "",
- v: false,
- init: function(){
- console.log("jtt: Grabbing config from " + __dirname + '/jtt-config.json');
- this.config = JSON.parse(fs.readFileSync(__dirname + '/jtt-config.json', 'utf8'));
- if(process.argv[2] && process.argv[2] === "v" || process.argv[2] === "verbose" || process.argv[2] === "-v" || process.argv[2] === "--verbose"){
- this.v = true;
- }
- if(this.config.useTree){
- this.getAPISchemaFromTree();
- } else {
- this.getSchemaFromFile();
- }
- },
- //starts logging and invokes necessary methods to start processing
- getAPISchemaFromTree: function(){
- console.log("jtt: ...");
- console.log("jtt: Building type definitions from directory: " + this.config.input);
- console.log("jtt: ...");
- this.generateFlatSchemas();
- this.processDirectory(this.config.input);
- if(this.v){
- console.log("jtt: ...");
- console.log("jtt: Resulting store:");
- console.log(JSON.stringify(this.namespaceStore));
- }
- console.log("jtt:");
- console.log("jtt: ...");
- console.log("jtt: Generating ts file");
- this.generateNativeTypes();
- if(this.v) {
- console.log("jtt: ...");
- console.log("jtt: Resulting types:");
- console.log(JSON.stringify(this.typeData));
- }
- this.saveTypes(this.typeData);
- },
- //extracts schemas for a schemas folder with no directories,
- //should not have endpoints here
- generateFlatSchemas: function(){
- const directory = this.slashit(this.config.input) + 'schemas/';
- console.log("jtt: Generating flat schemas from:", directory);
- fs.readdirSync(directory).forEach(item => {
- const dirStats = fs.statSync(directory + item);
- if(dirStats.isDirectory()) return;
- this.processFile(directory + item, this.namespaceStore);
- });
- },
- //recursively process directory to build endpoints and schemas, define method types, etc
- //builds up namespace store, args are used for recursion
- processDirectory: function(dir, endpoint = "", namespace = "", method = ""){
- const directory = this.slashit(dir);
- fs.readdirSync(directory).forEach(item => {
- if(item != "schemas"){
- const dirStats = fs.statSync(directory + item);
- if(dirStats.isDirectory()) {
- let methodName;
- // if this current is a request method we know all the previous
- // strings are the actual endpoint, so we can then store and name it
- if(this.isMethod(item)){
- methodName = item;
- namespace = this.buildAPIEndpointName(endpoint);
- if(!this.namespaceStore[namespace]) {
- this.namespaceStore[namespace] = {
- url: endpoint
- };
- }
- this.namespaceStore[namespace][item] = {
- request: undefined,
- response: undefined
- };
- console.log("jtt: ");
- console.log("jtt: Starting new namespace: ", namespace);
- console.log("jtt: at endpoint: ", endpoint);
- console.log("jtt: ");
- console.log("jtt: Generating schemas for:");
- } else {
- endpoint += '/' + item || "";
- }
- this.processDirectory(directory + item, endpoint, namespace, methodName)
- } else {
- this.processFile(directory + item, this.namespaceStore[namespace][method]);
- }
- }
- });
- return namespace;
- },
- //processes single file to extract schema definition
- processFile: function(file, store){
- const refMatch = new RegExp(/(?:\$ref)/g),
- fileData = JSON.parse(fs.readFileSync(file, 'utf8')),
- fileName = this.getFilename(file);
- console.log("jtt: " + file);
- let namespace = "";
- if(fileName === "request" || fileName === "response"){
- namespace = fileName;
- } else {
- namespace = this.buildAPIDefinitionName(fileName);
- }
- store[namespace] = {
- schema: {},
- required: fileData.required || [],
- pattern: fileData.pattern || "",
- defaults: fileData.defaults || {}
- };
- switch(fileData.type){
- case "string":
- store[namespace].schema = "string";
- break;
- case "object":
- for(let x in fileData.properties){
- if(fileData.properties[x]['$ref']){
- store[namespace].schema[x] = {
- "ref": this.buildAPIDefinitionName(fileData.properties[x]['$ref'])
- }
- } else {
- store[namespace].schema[x] = fileData.properties[x].type;
- }
- }
- };
- },
- //builds up fat string using ts syntax to define native types, namespaces, etc
- generateNativeTypes: function(){
- let string = "";
- for(let n in this.namespaceStore){
- string += "export namespace " + n + " { \r\n";
- //build flat def
- if(n.indexOf('_DEF') > -1) string += this.generateSchemaString(this.namespaceStore[n]);
- //build ep def
- if(n.indexOf('_EP') > -1) string += this.generateEndpointString(this.namespaceStore[n]);
- string += "}\r\n"
- }
- this.typeData = string;
- },
- generateEndpointString: function(namespace){
- let string = "";
- for(let prop in namespace){
- //build url prop
- if(prop === 'url') {
- string += '\xa0\ export const url = "' + namespace.url + '";\r\n';
- continue;
- }
- //all subsquent props will be request methods
- string += "\xa0\ export namespace " + prop + " { \r\n";
- for(let type in namespace[prop]){
- string += "\xa0\xa0\ export namespace " + type + " { \r\n";
- string += this.generateSchemaString(namespace[prop][type], "\xa0\xa0\xa0\ ");
- string += "\xa0\xa0\ }\r\n";
- }
- string += "\xa0\ }\r\n"
- }
- return string;
- },
- //generates all the string data the builds up a schema definition
- generateSchemaString: function(namespace, indent = ""){
- let string = indent + "\xa0\ export type schema = ";
- //build schema obj
- if(typeof namespace.schema === "object"){
- string += indent + "{ \r\n";
- for(let prop in namespace.schema){
- const isRequired = namespace.required.indexOf(prop) > -1;
- //if the prop in the schema is an obj, it's likely a reference
- if(typeof namespace.schema[prop] === "object"){
- if(namespace.schema[prop].ref){
- string += indent + '\xa0\xa0\ "' + prop +'"' + (!isRequired ? '?' : '') + ': ' + namespace.schema[prop].ref + '.schema, \r\n';
- }
- } else {
- string += indent + '\xa0\xa0\ "' + prop +'"' + (!isRequired ? '?' : '') + ': "' + namespace.schema[prop] + '", \r\n';
- }
- //TODO: add array option here
- }
- string += indent + "\xa0\ }\r\n"
- //if schema is simply string, do so
- } else if (typeof namespace.schema === "string") {
- string += "String;\r\n";
- }
- //build required arr
- if(namespace.required && namespace.required.length > 0){
- string += indent + "\xa0\ export const required = [\r\n";
- namespace.required.map((v) => string += indent + '\xa0\xa0\ "' + v + '", \r\n');
- string += indent + "\xa0\ ];\r\n"
- }
- //build defs Object
- if(namespace.defaults && Object.keys(namespace.defaults).length > 0){
- string += indent + "\xa0\ export const defaults = {\r\n";
- Object.keys(namespace.defaults).map((v)=>{
- const isString = typeof namespace.defaults[v] === "string";
- string += indent + '\xa0\xa0\ "' + v + '": ';
- string += isString ? '"' : '';
- string += namespace.defaults[v];
- string += isString ? '"' : '';
- string += ', \r\n';
- });
- string += indent + "\xa0\ }\r\n"
- }
- //build patterns obj
- if(namespace.pattern && namespace.pattern !== ""){
- string += indent + '\xa0\ export const pattern = "' + namespace.pattern + '";\r\n';
- }
- return string;
- },
- //decides whether or not we're in the last it of a loop to place a comma or not
- comma: function(){},
- //builds namespace name for a single api schema def,
- //just builds a name, no actual string data
- buildAPIDefinitionName: function(fileName){
- return fileName.toUpperCase() + "_DEF";
- },
- //builds the namespace name, just the name itself, not the actual
- //string data used for the native types so no brackets, no export, etc
- buildAPIEndpointName: function(str){
- let bracketReg = new RegExp(/{(.*?)}/g),
- segments = str.split('/'),
- name = "";
- //accounting for str that started with a slash
- if(segments[0] === "") segments.shift();
- for(let x = 0; x < segments.length; x++){
- //if current segment is a variable, we currently put an
- //underscore but there could be better ways
- if(bracketReg.test(segments[x])) {
- name += segments[x].replace("{", "").replace("}", "").replace("-", "") + "_";
- } else {
- name += segments[x].toUpperCase() + "_";
- }
- }
- return name += "EP";
- },
- //adds slash to end of string
- slashit: function(string){
- return string[string - 1] !== '/' ? string += '/' : string;
- },
- //checks if string is a type of req method as per methods arr
- isMethod: function(string){
- for(let x = 0; x < methods.length; x++){
- if(string === methods[x]){
- return true;
- }
- }
- return false;
- },
- //extracts filename from string
- getFilename: function(str){
- const segs = /(.*?)\.json/g.exec(str)[1].split('/');
- return segs[segs.length-1];
- },
- saveTypes: function(data){
- console.log("jtt: ...");
- console.log("jtt: Saving types to " + this.config.output + this.config.filename + ".ts");
- fs.writeFileSync(this.config.output + this.config.filename + ".ts", data, function(){
- console.log("jtt: File saved");
- }, function(err) {
- if(err) {
- return console.error(err);
- }
- console.log("The file was saved!");
- });
- }
- }
- jtt.init();
- exports.jtt = jtt;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement