UNPKG

quick-erd

Version:

quick and easy text-based ERD + code generator for migration, query, typescript types and orm entity

300 lines (299 loc) 9.13 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Parser = void 0; exports.parse = parse; const enum_1 = require("./enum"); const meta_1 = require("./meta"); function parse(input) { const parser = new Parser(); parser.parse(input); return parser; } class Parser { table_list = []; line_list = []; zoom; view; textBgColor; diagramTextColor; textColor; diagramBgColor; tableBgColor; tableTextColor; parse(input) { input.split('\n').forEach(line => { line = line .trim() .replace(/#.*/, '') .replace(/\/\/.*/, '') .trim(); if (!line) return; this.line_list.push(line); }); this.table_list = []; while (this.hasTable()) { this.table_list.push(this.parseTable()); } this.parseMeta(input); } parseMeta(input) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain const zoom = +input.match(meta_1.zoomValueRegex)?.[1]; if (zoom) this.zoom = zoom; const view = input.match(meta_1.viewPositionRegex); if (view) this.view = { x: +view[1], y: +view[2] }; const textBgColor = input.match(meta_1.textBgColorRegex); if (textBgColor) this.textBgColor = textBgColor[1]; const textColor = input.match(meta_1.textColorRegex); if (textColor) this.textColor = textColor[1]; const diagramBgColor = input.match(meta_1.diagramBgColorRegex); if (diagramBgColor) this.diagramBgColor = diagramBgColor[1]; const diagramTextColor = input.match(meta_1.diagramTextColorRegex); if (diagramTextColor) this.diagramTextColor = diagramTextColor[1]; const tableBgColor = input.match(meta_1.tableBgColorRegex); if (tableBgColor) this.tableBgColor = tableBgColor[1]; const tableTextColor = input.match(meta_1.tableTextColorRegex); if (tableTextColor) this.tableTextColor = tableTextColor[1]; input.match(meta_1.tableNameRegex_g)?.forEach(line => { const match = line.match(meta_1.tableNameRegex) || []; const name = match[1]; const x = +match[2]; const y = +match[3]; const color = match[4]; const table = this.table_list.find(table => table.name == name); if (table) table.position = { x, y, color }; }); } peekLine() { if (this.line_list.length === 0) { throw new Error('no reminding line'); } return this.line_list[0]; } hasTable() { while (this.line_list[0] === '') this.line_list.shift(); return this.line_list[0] && this.line_list[1]?.startsWith('-'); } parseTable() { const name = this.parseName(); this.parseEmptyLine(); this.skipLine('-'); const field_list = parseAll(() => { // skip empty lines if (this.hasTable()) { throw new Error('end of table'); } return this.parseField(); }); const has_primary_key = field_list.some(field => field.is_primary_key); if (!has_primary_key) { const field = field_list.find(field => field.name === 'id'); if (field) { field.is_primary_key = true; } } return { name, field_list }; } parseField() { const field_name = this.parseName(); let type = ''; let is_null = false; let is_unique = false; let is_primary_key = false; let is_unsigned = false; let default_value; let references; for (;;) { const name = this.parseType(); if (!name) break; switch (name.toUpperCase()) { case 'NULL': is_null = true; continue; case 'UNIQUE': is_unique = true; continue; case 'UNSIGNED': is_unsigned = true; continue; case 'DEFAULT': // TODO parse default value default_value = this.parseDefaultValue(); continue; case 'PK': is_primary_key = true; continue; case 'FK': references = this.parseForeignKeyReference(field_name); continue; default: if (type) { console.debug('unexpected token:', { field_name, type, token: name, }); continue; } type = name; } } type ||= defaultFieldType; this.skipLine(); return { name: field_name, type, is_null, is_unique, is_primary_key, is_unsigned, default_value, references, }; } skipLine(line = '') { if (this.line_list[0]?.startsWith(line)) { this.line_list.shift(); } } parseEmptyLine() { const line = this.line_list[0]?.trim(); if (line !== '') { throw new NonEmptyLineError(line); } this.line_list.shift(); } parseName() { let line = this.peekLine(); const match = line.match(/[a-zA-Z0-9_]+/); if (!match) { throw new ParseNameError(line); } const name = match[0]; line = line.replace(name, '').trim(); this.line_list[0] = line; return name; } parseType() { let line = this.peekLine(); let match = line.match(/^not null\s*/i); if (match) { line = line.slice(match[0].length); } match = line.match(/^\w+\(.*?\)/) || line.match(/^[a-zA-Z0-9_(),"']+/); if (!match) { return; } const name = match[0]; line = line.replace(name, '').trim(); this.line_list[0] = line; if (name.match(/^enum/i)) { return (0, enum_1.formatEnum)(name); } return name; } parseDefaultValue() { let line = this.peekLine(); let end; if (line[0] === '"') { end = line.indexOf('"', 1) + 1; } else if (line[0] === "'") { end = line.indexOf("'", 1) + 1; } else if (line[0] === '`') { end = line.indexOf('`', 1) + 1; } else if (line.includes(' ')) { end = line.indexOf(' '); } else { end = line.length - 1; } const value = line.slice(0, end + 1); line = line.replace(value, '').trim(); this.line_list[0] = line; return value; } parseRelationType() { let line = this.peekLine(); const match = line.match(/.* /); if (!match) { throw new ParseRelationTypeError(line); } const type = match[0].trim(); line = line.replace(match[0], '').trim(); this.line_list[0] = line; return type; } parseForeignKeyReference(ref_field_name) { if (ref_field_name.endsWith('_id') && this.peekLine() === '') { return { table: ref_field_name.replace(/_id$/, ''), field: 'id', type: defaultRelationType, }; } const type = this.parseRelationType(); const table = this.parseName(); const line = this.peekLine(); let field; if (line == '') { field = 'id'; } else if (line.startsWith('.')) { this.line_list[0] = line.slice(1); field = this.parseName(); } else { throw new ParseForeignKeyReferenceError(line); } return { type, table, field }; } } exports.Parser = Parser; class NonEmptyLineError extends Error { } class LineError extends Error { line; constructor(line, message) { super(message); this.line = line; } } class ParseNameError extends LineError { } class ParseRelationTypeError extends LineError { } class ParseForeignKeyReferenceError extends LineError { line; constructor(line) { super(line, `expect '.', got '${line[0]}'`); this.line = line; } } function parseAll(fn) { const result_list = []; for (;;) { try { result_list.push(fn()); } catch (error) { return result_list; } } } const defaultFieldType = 'integer'; const defaultRelationType = '>0-';