@cyklang/cli
Advanced tools
Comparing version 0.1.2 to 0.2.0
@@ -1,7 +0,8 @@ | ||
import { DBManager } from "@cyklang/core"; | ||
import { DBManager, DBRemote } from "@cyklang/core"; | ||
import { Command } from "commander"; | ||
export declare class Cmd extends Command { | ||
dbManager: DBManager | undefined; | ||
dbRemote: DBRemote | undefined; | ||
constructor(name: string); | ||
prologue(options: any): Promise<void>; | ||
} |
@@ -28,6 +28,6 @@ "use strict"; | ||
const nodeCrypto = new NodeCrypto_1.NodeCrypto(); | ||
const dbRemote = new core_1.DBRemote(structure.scope, process.env.DBREMOTE_URL, nodeCrypto); | ||
const login = await dbRemote.signin(process.env.USER_NAME, undefined, process.env.USER_PASSWORD); | ||
this.dbRemote = new core_1.DBRemote(structure.scope, process.env.DBREMOTE_URL, nodeCrypto); | ||
const login = await this.dbRemote.signin(process.env.USER_NAME, undefined, process.env.USER_PASSWORD); | ||
// logger.debug(login) | ||
this.dbManager = new core_1.DBManager(structure.scope, dbRemote); | ||
this.dbManager = new core_1.DBManager(structure.scope, this.dbRemote); | ||
await this.dbManager.initialize(); | ||
@@ -34,0 +34,0 @@ } |
@@ -9,3 +9,4 @@ import { DBManager } from "@cyklang/core"; | ||
where?: string | undefined; | ||
width?: string | undefined; | ||
}): Promise<void>; | ||
} |
@@ -59,2 +59,4 @@ "use strict"; | ||
const list = new List(); | ||
if (options.width !== undefined) | ||
list.width = options.width; | ||
for (let ind = 0; ind < fields.length; ind++) { | ||
@@ -82,2 +84,3 @@ const field = fields[ind]; | ||
constructor() { | ||
this.width = "0"; | ||
this.columns = []; | ||
@@ -107,12 +110,31 @@ this.rows = []; | ||
renderList(title) { | ||
// calculate column.width | ||
// parse width option | ||
const colws = []; | ||
const colws_ = this.width.split(','); | ||
for (let ind = 0; ind < colws_.length; ind++) { | ||
const colw = Number.parseInt(colws_[ind]); | ||
if (isNaN(colw)) { | ||
const msg = '--width incorrect format : ' + this.width; | ||
throw msg; | ||
} | ||
colws.push(colw); | ||
} | ||
logger.debug('colws', colws.join(',')); | ||
// calculate column.actualWidth | ||
let total_width = 0; | ||
for (let indi = 0; indi < this.columns.length; indi++) { | ||
const column = this.columns[indi]; | ||
column.width = column.label.length; | ||
if (indi < colws.length) | ||
column.width = colws[indi]; | ||
else | ||
column.width = colws[colws.length - 1]; | ||
column.actualWidth = column.label.length; | ||
this.rows.forEach((row) => { | ||
const cell = row.cells[indi]; | ||
const renderedCell = this.renderCell(cell); | ||
if (renderedCell.length > column.width) { | ||
column.width = renderedCell.length; | ||
if (renderedCell.length > column.actualWidth) { | ||
if (column.width === 0 || renderedCell.length < column.width) | ||
column.actualWidth = renderedCell.length; | ||
else | ||
column.actualWidth = column.width; | ||
} | ||
@@ -122,3 +144,3 @@ }); | ||
total_width += 3; | ||
total_width += column.width; | ||
total_width += column.actualWidth; | ||
} | ||
@@ -135,2 +157,4 @@ // title | ||
} | ||
this.lines.push(this.renderSeparator()); | ||
this.lines.push('Number of lines : ' + this.rows.length); | ||
return this.lines.join('\n'); | ||
@@ -144,3 +168,3 @@ } | ||
result += ' | '; | ||
result += this.renderCell(column.label, column.width, 'center'); | ||
result += this.renderCell(column.label, column.actualWidth, 'center'); | ||
} | ||
@@ -159,3 +183,3 @@ return result; | ||
result += ' | '; | ||
result += this.renderCell(row.cells[indi], column.width, justify); | ||
result += this.renderCell(row.cells[indi], column.actualWidth, justify); | ||
} | ||
@@ -170,3 +194,3 @@ return result; | ||
result += '-+-'; | ||
result += '-'.repeat(column.width); | ||
result += '-'.repeat(column.actualWidth); | ||
} | ||
@@ -183,16 +207,25 @@ return result; | ||
} | ||
else if (value instanceof Date) { | ||
const d = value; | ||
result = d.toISOString(); | ||
} | ||
else { | ||
result = value.toString(); | ||
} | ||
if (width !== undefined && result.length < width) { | ||
if (justify === undefined || justify === 'left') { | ||
result += ' '.repeat(width - result.length); | ||
if (width !== undefined && width !== 0) { | ||
if (result.length >= width) { | ||
result = result.substring(0, width); | ||
} | ||
else if (justify === 'right') { | ||
result = ' '.repeat(width - result.length) + result; | ||
} | ||
else { | ||
// justify === center | ||
const before = Math.floor((width - result.length) / 2); | ||
result = ' '.repeat(before) + result + ' '.repeat(width - result.length - before); | ||
if (justify === undefined || justify === 'left') { | ||
result += ' '.repeat(width - result.length); | ||
} | ||
else if (justify === 'right') { | ||
result = ' '.repeat(width - result.length) + result; | ||
} | ||
else { | ||
// justify === center | ||
const before = Math.floor((width - result.length) / 2); | ||
result = ' '.repeat(before) + result + ' '.repeat(width - result.length - before); | ||
} | ||
} | ||
@@ -207,2 +240,3 @@ } | ||
this.width = 0; | ||
this.actualWidth = 0; | ||
this.name = name; | ||
@@ -209,0 +243,0 @@ } |
@@ -16,2 +16,3 @@ #!/usr/bin/env node | ||
const InitCommand_1 = require("./InitCommand"); | ||
const AssetCommand_1 = require("./AssetCommand"); | ||
const logger = loglevel_1.default.getLogger("index.ts"); | ||
@@ -23,2 +24,4 @@ logger.setLevel("debug"); | ||
program.addCommand(new InitCommand_1.InitCommand('init')); | ||
program.addCommand(new AssetCommand_1.AssetCommand('asset', 'manage assets')); | ||
program.addCommand(new AssetCommand_1.AssetCommand('a', 'manage assets')); | ||
program.addCommand(new ModuleCommand_1.ModuleCommand('module')); | ||
@@ -25,0 +28,0 @@ program.addCommand(new ModuleCommand_1.ModuleCommand('m')); |
@@ -56,6 +56,6 @@ #!/usr/bin/env ts-node | ||
function getModuleDBName(filePath) { | ||
let result; | ||
if (filePath.endsWith('.xml') === false) | ||
throw filePath + ' does not have .xml extension'; | ||
result = path_1.default.basename(filePath, '.xml'); | ||
// let result | ||
// if (filePath.endsWith('.xml') === false) throw filePath + ' does not have .xml extension' | ||
// result = path.basename(filePath, '.xml') | ||
const result = path_1.default.parse(filePath).name; | ||
const identifierRegex = /^[_a-zA-Z][_a-zA-Z0-9]*$/; | ||
@@ -70,3 +70,3 @@ if (result.match(identifierRegex) === null) | ||
this.description(description) | ||
.option('-i --id <id>', 'module ID to download') | ||
.option('-i --id <id>', 'module ID to upload') | ||
.argument('[files...]', 'local module file(s) to upload to the server') | ||
@@ -176,3 +176,3 @@ .action(async (files, options) => { | ||
if (fs.existsSync(filePath) === true) { | ||
logger.info('file ' + filePath + ' already exists and will not be overwritten '); | ||
logger.info('file ' + filePath + ' already exists. Remove it before launching the command if you want to overwrite.'); | ||
} | ||
@@ -179,0 +179,0 @@ else { |
@@ -32,2 +32,4 @@ #!/usr/bin/env ts-node | ||
const Cmd_1 = require("./Cmd"); | ||
const DBClient_1 = require("./DBClient"); | ||
const form_data_1 = __importDefault(require("form-data")); | ||
const logger = loglevel_1.default.getLogger("TableCommand.ts"); | ||
@@ -40,2 +42,7 @@ logger.setLevel("debug"); | ||
this.addCommand(new TableExportCmd()); | ||
this.addCommand(new TableImportCmd()); | ||
this.addCommand(new TableList('list', 'list database tables')); | ||
this.addCommand(new TableList('l', '(l)ist database tables')); | ||
this.addCommand(new TableQuery('query', 'query select table content')); | ||
this.addCommand(new TableQuery('q', 'query select table content')); | ||
} | ||
@@ -57,4 +64,46 @@ } | ||
throw 'dbManager undefined'; | ||
await commandImportExport(this.dbManager, 'to', tables, options); | ||
// await commandImportExport(this.dbManager, 'to', tables, options) | ||
try { | ||
if (options.file === undefined && options.dir === undefined) | ||
throw '--file or --dir mandatory'; | ||
if (options.file !== undefined && options.dir !== undefined) | ||
throw '--file and --dir are mutually exclusive'; | ||
if (tables.length === 0) | ||
throw 'at least one table name is mandatory'; | ||
if (tables.length > 1 && options.dir === undefined) | ||
throw 'for multiple tables, --dir is mandatory'; | ||
for (let indi = 0; indi < tables.length; indi++) { | ||
const tableName = tables[indi]; | ||
const dbTable = await this.dbManager.dbTableExist(tableName); | ||
if (dbTable === undefined) { | ||
logger.info('table ' + tableName + ' not found'); | ||
continue; | ||
} | ||
const filename = buildFilename(dbTable.name, options); | ||
await this.copyTo(dbTable, filename); | ||
} | ||
} | ||
catch (err) { | ||
logger.error(err); | ||
} | ||
} | ||
async copyTo(dbTable, filename) { | ||
if (this.dbRemote === undefined) | ||
throw 'dbRemote undefined'; | ||
try { | ||
const writeStream = fs.createWriteStream(filename); | ||
const response = await this.dbRemote.apiServer.get('/admin/export/' + dbTable.name, { responseType: 'stream' }); | ||
const stream = response; | ||
stream.on('data', (data) => { | ||
logger.debug('**** DATA ****', data.length); | ||
const textDecoder = new TextDecoder(); | ||
const chunk = (textDecoder.decode(data)); | ||
writeStream.write(chunk); | ||
}); | ||
stream.on('end', () => logger.debug('**** DONE ****')); | ||
} | ||
catch (err) { | ||
logger.error(err); | ||
} | ||
} | ||
} | ||
@@ -65,2 +114,3 @@ class TableImportCmd extends Cmd_1.Cmd { | ||
this.description('import from CSV file(s)') | ||
.argument('<tables...>', 'table(s) to import') | ||
.option('-f --file <file>', 'name of the file to import, defaults to <table_name>.csv') | ||
@@ -74,5 +124,68 @@ .option('-d --dir <directory>', 'name of the directory where to find files to import') | ||
throw 'dbManager undefined'; | ||
await commandImportExport(this.dbManager, 'from', tables, options); | ||
// await commandImportExport(this.dbManager, 'to', tables, options) | ||
try { | ||
if (options.file === undefined && options.dir === undefined) | ||
throw '--file or --dir mandatory'; | ||
if (options.file !== undefined && options.dir !== undefined) | ||
throw '--file and --dir are mutually exclusive'; | ||
if (tables.length === 0) | ||
throw 'at least one table name is mandatory'; | ||
if (tables.length > 1 && options.dir === undefined) | ||
throw 'for multiple tables, --dir is mandatory'; | ||
for (let indi = 0; indi < tables.length; indi++) { | ||
const tableName = tables[indi]; | ||
const dbTable = await this.dbManager.dbTableExist(tableName); | ||
if (dbTable === undefined) { | ||
logger.info('table ' + tableName + ' not found'); | ||
continue; | ||
} | ||
const filename = buildFilename(dbTable.name, options); | ||
this.copyFrom(dbTable, filename); | ||
} | ||
} | ||
catch (err) { | ||
logger.error(err); | ||
} | ||
} | ||
async copyFrom(dbTable, filename) { | ||
if (this.dbRemote === undefined) | ||
throw 'dbRemote undefined'; | ||
try { | ||
const file = fs.createReadStream(filename); | ||
const form = new form_data_1.default(); | ||
form.append('uploadFile', file); | ||
const route = '/admin/import/' + dbTable.name; | ||
const resp = await this.dbRemote.apiServer.post(route, form); | ||
if (resp.status === 200) { | ||
logger.debug(filename + ' uploaded'); | ||
} | ||
} | ||
catch (err) { | ||
logger.error(err); | ||
} | ||
} | ||
} | ||
function buildFilename(tableName, options) { | ||
let filename; | ||
if (options.file !== undefined) { | ||
if (fs.existsSync(options.file) && fs.lstatSync(options.file).isDirectory()) | ||
throw '--file ' + options.file + ' is a directory'; | ||
filename = options.file; | ||
} | ||
else if (options.dir === undefined) | ||
throw '--file and --dir unspecified'; | ||
else if (fs.existsSync(options.dir) === false) | ||
throw 'directory ' + options.dir + ' does not exist'; | ||
else if (fs.lstatSync(options.dir).isFile()) { | ||
throw ("--dir " + options.dir + " is a file "); | ||
} | ||
else { | ||
let directory = options.dir.trim(); | ||
if (directory[directory.length - 1] !== '/') { | ||
directory += '/'; | ||
} | ||
filename = directory + tableName + '.csv'; | ||
} | ||
return filename; | ||
} | ||
const commandImportExport = async (dbManager, direction, tables, options) => { | ||
@@ -96,51 +209,128 @@ logger.debug('tables : ', tables, 'options : ', options); | ||
} | ||
let lcols; | ||
for (let indj = 0; indj < dbTable.columns.length; indj++) { | ||
const dbColumn = dbTable.columns[indj]; | ||
if (lcols === undefined) { | ||
lcols = "("; | ||
psqlCmd(dbTable, direction, options); | ||
} | ||
} | ||
catch (err) { | ||
logger.error(err); | ||
} | ||
}; | ||
//-------------------------------------------------------------------------------------------------------------------- | ||
// function psqlCmd | ||
// invoke local install of psql CLI | ||
//-------------------------------------------------------------------------------------------------------------------- | ||
function psqlCmd(dbTable, direction, options) { | ||
let lcols; | ||
for (let indj = 0; indj < dbTable.columns.length; indj++) { | ||
const dbColumn = dbTable.columns[indj]; | ||
if (lcols === undefined) { | ||
lcols = "("; | ||
} | ||
else { | ||
lcols += ","; | ||
} | ||
lcols += dbColumn.name; | ||
} | ||
lcols += ")"; | ||
let psql_cmd = "psql -c"; | ||
if (process.env.HEROKU_CLI !== undefined) { | ||
psql_cmd = process.env.HEROKU_CLI + ' -c '; | ||
} | ||
let filename; | ||
if (options.file !== undefined) { | ||
if (fs.existsSync(options.file) && fs.lstatSync(options.file).isDirectory()) | ||
throw '--file ' + options.file + ' is a directory'; | ||
filename = options.file; | ||
} | ||
else if (options.dir === undefined) | ||
throw '--file and --dir unspecified'; | ||
else if (fs.existsSync(options.dir) === false) | ||
throw 'directory ' + options.dir + ' does not exist'; | ||
else if (fs.lstatSync(options.dir).isFile()) { | ||
logger.debug("--dir " + options.dir + " is a file "); | ||
} | ||
else { | ||
let directory = options.dir.trim(); | ||
if (directory[directory.length - 1] !== '/') { | ||
directory += '/'; | ||
} | ||
filename = directory + dbTable.name + '.csv'; | ||
} | ||
let copy_cmd = "\\copy " + dbTable.name + lcols + direction + " '" + filename + "' csv header"; | ||
const shell_cmd = psql_cmd + '"' + copy_cmd + '"'; | ||
logger.debug(shell_cmd); | ||
const child = (0, child_process_1.spawn)(shell_cmd.trim(), { stdio: 'inherit', shell: true }); | ||
} | ||
//-------------------------------------------------------------------------------------------------------------------- | ||
// class TableList | ||
//-------------------------------------------------------------------------------------------------------------------- | ||
class TableList extends Cmd_1.Cmd { | ||
constructor(name, description) { | ||
super(name); | ||
this.description(description) | ||
.option('-s --sort <columns>', 'sort list by column numbers (begins with 0) separated by comma') | ||
.action(async (options) => { | ||
await this.commandList(options); | ||
}); | ||
} | ||
async commandList(options) { | ||
await this.prologue(options); | ||
if (this.dbManager === undefined) | ||
throw 'dbManager undefined'; | ||
const dbClient = new DBClient_1.DBClient(this.dbManager); | ||
dbClient.selectFromTable('List of Tables', 'cyk_table', { fields: 'table_id,table_name,table_description,table_access', sort: options.sort || '1' }); | ||
} | ||
} | ||
//-------------------------------------------------------------------------------------------------------------------- | ||
// class TableQuery | ||
//-------------------------------------------------------------------------------------------------------------------- | ||
class TableQuery extends Cmd_1.Cmd { | ||
constructor(name, description) { | ||
super(name); | ||
this.description(description) | ||
.argument('<table>', 'table to query') | ||
.option('--where <clause_where_sql>', 'criteria in SQL syntax') | ||
.option('-s --sort <columns>', 'sort by columns positions (begins by 0) separated by comma') | ||
.option('-w --width <colwidth>', 'columns widths separated by comma') | ||
.action(async (table, options) => { | ||
this.commandQuery(table, options); | ||
}); | ||
} | ||
async commandQuery(table, options) { | ||
try { | ||
await this.prologue(options); | ||
if (this.dbManager === undefined) | ||
throw 'dbManager undefined'; | ||
const dbTable = await this.dbManager.dbTableExist(table); | ||
if (dbTable === undefined) | ||
throw 'table ' + table + ' not found'; | ||
let fields = ''; | ||
for (let ind = 0; ind < dbTable.columns.length; ind++) { | ||
const dbColumn = dbTable.columns[ind]; | ||
let ok = false; | ||
let fieldName = dbColumn.name; | ||
if (dbColumn.dbType === 'text' || dbColumn.dbType === 'bytea') { | ||
ok = false; | ||
fieldName = 'length(' + dbColumn.name + ')'; | ||
} | ||
else { | ||
lcols += ","; | ||
ok = true; | ||
} | ||
lcols += dbColumn.name; | ||
if (ok === true) { | ||
if (fields !== '') | ||
fields += ','; | ||
fields += fieldName; | ||
} | ||
} | ||
lcols += ")"; | ||
let psql_cmd = "psql -c"; | ||
if (process.env.HEROKU_CLI !== undefined) { | ||
psql_cmd = process.env.HEROKU_CLI + ' -c '; | ||
logger.debug('commandQuery', table); | ||
const dbClient = new DBClient_1.DBClient(this.dbManager); | ||
let title = 'Table ' + table; | ||
if (options.where !== undefined) { | ||
title += ' where ' + options.where; | ||
} | ||
let filename; | ||
if (options.file !== undefined) { | ||
if (fs.existsSync(options.file) && fs.lstatSync(options.file).isDirectory()) | ||
throw '--file ' + options.file + ' is a directory'; | ||
filename = options.file; | ||
} | ||
else if (options.dir === undefined) | ||
throw '--file and --dir unspecified'; | ||
else if (fs.existsSync(options.dir) === false) | ||
throw 'directory ' + options.dir + ' does not exist'; | ||
else if (fs.lstatSync(options.dir).isFile()) { | ||
logger.debug("--dir " + options.dir + " is a file "); | ||
} | ||
else { | ||
let directory = options.dir.trim(); | ||
if (directory[directory.length - 1] !== '/') { | ||
directory += '/'; | ||
} | ||
filename = directory + tableName + '.csv'; | ||
} | ||
let copy_cmd = "\\copy " + tableName + lcols + direction + " '" + filename + "' csv header"; | ||
const shell_cmd = psql_cmd + '"' + copy_cmd + '"'; | ||
logger.debug(shell_cmd); | ||
const child = (0, child_process_1.spawn)(shell_cmd.trim(), { stdio: 'inherit', shell: true }); | ||
// exec(shell_cmd.trim(), (error, stdout, stderr) => { | ||
// if (error) throw error | ||
// logger.debug(stdout, stderr) | ||
// }) | ||
dbClient.selectFromTable(title, table, { fields: fields, width: options.width, sort: options.sort || '1', where: options.where }); | ||
} | ||
catch (err) { | ||
logger.error(err); | ||
} | ||
} | ||
catch (err) { | ||
logger.error(err); | ||
} | ||
}; | ||
} |
{ | ||
"name": "@cyklang/cli", | ||
"version": "0.1.2", | ||
"version": "0.2.0", | ||
"description": "cyklang CLI", | ||
@@ -14,3 +14,3 @@ "main": "build/index.js", | ||
"dev": "nodemon -e ts --ignore build --exec \"npm run global\" ", | ||
"test": "sudo npm i -g && cyk" | ||
"test": "cyk test test/t*.xml" | ||
}, | ||
@@ -24,3 +24,5 @@ "keywords": [], | ||
"dotenv": "^16.0.1", | ||
"form-data": "^4.0.0", | ||
"loglevel": "^1.8.0", | ||
"mime-types": "^2.1.35", | ||
"pako": "^2.0.4", | ||
@@ -30,2 +32,3 @@ "xml": "^1.0.1" | ||
"devDependencies": { | ||
"@types/mime-types": "^2.1.1", | ||
"@types/pako": "^2.0.0", | ||
@@ -32,0 +35,0 @@ "@types/sax": "^1.2.4", |
@@ -11,2 +11,3 @@ import { DBManager, DBRemote, Structure } from "@cyklang/core"; | ||
dbManager: DBManager | undefined | ||
dbRemote: DBRemote | undefined | ||
@@ -27,9 +28,9 @@ constructor(name: string) { | ||
const nodeCrypto = new NodeCrypto() | ||
const dbRemote = new DBRemote(structure.scope, process.env.DBREMOTE_URL, nodeCrypto) | ||
const login = await dbRemote.signin(process.env.USER_NAME, undefined, process.env.USER_PASSWORD) | ||
this.dbRemote = new DBRemote(structure.scope, process.env.DBREMOTE_URL, nodeCrypto) | ||
const login = await this.dbRemote.signin(process.env.USER_NAME, undefined, process.env.USER_PASSWORD) | ||
// logger.debug(login) | ||
this.dbManager = new DBManager(structure.scope, dbRemote) | ||
this.dbManager = new DBManager(structure.scope, this.dbRemote) | ||
await this.dbManager.initialize() | ||
} | ||
} |
@@ -14,3 +14,3 @@ import { BasicType, DBColumn, DBExecuteRequest, DBManager, DBTable, ObjectData, parseXML, PrimitiveData } from "@cyklang/core"; | ||
async selectFromTable(title: string, tableName: string, options: | ||
{ fields: string, sort?: string | undefined, where?: string | undefined }) { | ||
{ fields: string, sort?: string | undefined, where?: string | undefined, width?: string | undefined }) { | ||
@@ -30,3 +30,3 @@ try { | ||
let order_by = '' | ||
for (let ind = 0; ind < sortcols.length; ind++ ) { | ||
for (let ind = 0; ind < sortcols.length; ind++) { | ||
const coli = sortcols[ind] | ||
@@ -37,3 +37,3 @@ const colname = colnames[Number(coli)] | ||
} | ||
logger.debug('order_by '+order_by) | ||
logger.debug('order_by ' + order_by) | ||
request.parameters.addVariable('order_by', stringDataType).data = new PrimitiveData(stringDataType, order_by) | ||
@@ -48,7 +48,7 @@ } | ||
// } | ||
const xmlResult = await this.dbManager.dbExecute(request) | ||
if (xmlResult === undefined) throw 'xmlResult undefined' | ||
const tagXmlResult = parseXML('xmlResult', xmlResult) | ||
const objXmlResult = (await objectDataType.parseData(tagXmlResult, this.dbManager.scope)) as ObjectData | ||
@@ -59,9 +59,10 @@ const objectMeta = (objXmlResult.variables.getData('meta')) as ObjectData | ||
if (xmlMeta === undefined) throw 'meta does not have ' + tableName + ' description' | ||
const tagDBTable = parseXML('dbTable', xmlMeta) | ||
const dbTable = new DBTable(tagDBTable) | ||
const fields = options.fields.split(',') | ||
const list = new List() | ||
if (options.width !== undefined) list.width = options.width | ||
for (let ind = 0; ind < fields.length; ind++) { | ||
@@ -73,3 +74,3 @@ const field = fields[ind] | ||
} | ||
const objDataset = objXmlResult.variables.getData(tableName) as ObjectData | ||
@@ -81,3 +82,3 @@ for (let ind = 0; ind < objDataset.variables.length(); ind++) { | ||
logger.info(list.renderList(title)) | ||
} | ||
@@ -91,2 +92,3 @@ catch (err) { | ||
class List { | ||
width = "0" | ||
columns: Column[] = [] | ||
@@ -118,16 +120,40 @@ rows: Row[] = [] | ||
renderList(title: string): string { | ||
// calculate column.width | ||
// parse width option | ||
const colws: number[] = [] | ||
const colws_ = this.width.split(',') | ||
for (let ind = 0; ind < colws_.length; ind++) { | ||
const colw = Number.parseInt(colws_[ind]) | ||
if (isNaN(colw)) { | ||
const msg = '--width incorrect format : ' + this.width | ||
throw msg | ||
} | ||
colws.push(colw) | ||
} | ||
logger.debug('colws', colws.join(',')) | ||
// calculate column.actualWidth | ||
let total_width = 0 | ||
for (let indi = 0; indi < this.columns.length; indi++) { | ||
const column = this.columns[indi] | ||
column.width = column.label.length | ||
if (indi < colws.length) column.width = colws[indi] | ||
else column.width = colws[colws.length - 1] | ||
column.actualWidth = column.label.length | ||
this.rows.forEach((row) => { | ||
const cell = row.cells[indi] | ||
const renderedCell = this.renderCell(cell) | ||
if (renderedCell.length > column.width) { | ||
column.width = renderedCell.length | ||
if (renderedCell.length > column.actualWidth) { | ||
if (column.width === 0 || renderedCell.length < column.width) | ||
column.actualWidth = renderedCell.length | ||
else | ||
column.actualWidth = column.width | ||
} | ||
}) | ||
if (total_width > 0) total_width += 3 | ||
total_width += column.width | ||
total_width += column.actualWidth | ||
} | ||
@@ -143,6 +169,9 @@ | ||
// rows | ||
for (let indj = 0; indj < this.rows.length; indj++ ) { | ||
for (let indj = 0; indj < this.rows.length; indj++) { | ||
const row = this.rows[indj] | ||
this.lines.push(this.renderRow(row)) | ||
} | ||
this.lines.push(this.renderSeparator()) | ||
this.lines.push('Number of lines : ' + this.rows.length) | ||
return this.lines.join('\n') | ||
@@ -153,6 +182,6 @@ } | ||
let result = '' | ||
for (let indi = 0; indi < this.columns.length; indi++ ) { | ||
for (let indi = 0; indi < this.columns.length; indi++) { | ||
const column = this.columns[indi] | ||
if (result !== '') result += ' | ' | ||
result += this.renderCell(column.label, column.width, 'center') | ||
result += this.renderCell(column.label, column.actualWidth, 'center') | ||
} | ||
@@ -164,3 +193,3 @@ return result | ||
let result = '' | ||
for (let indi = 0; indi < this.columns.length; indi++ ) { | ||
for (let indi = 0; indi < this.columns.length; indi++) { | ||
const column = this.columns[indi] | ||
@@ -172,3 +201,3 @@ let justify: 'left' | 'right' | 'center' | undefined = 'left' | ||
if (result !== '') result += ' | ' | ||
result += this.renderCell(row.cells[indi], column.width, justify) | ||
result += this.renderCell(row.cells[indi], column.actualWidth, justify) | ||
} | ||
@@ -180,6 +209,6 @@ return result | ||
let result = '' | ||
for ( let indi = 0; indi < this.columns.length; indi++ ) { | ||
for (let indi = 0; indi < this.columns.length; indi++) { | ||
const column = this.columns[indi] | ||
if (result !== '') result += '-+-' | ||
result += '-'.repeat(column.width) | ||
result += '-'.repeat(column.actualWidth) | ||
} | ||
@@ -197,16 +226,25 @@ return result | ||
} | ||
else if (value instanceof Date) { | ||
const d = value as Date | ||
result = d.toISOString() | ||
} | ||
else { | ||
result = value.toString() | ||
} | ||
if (width !== undefined && result.length < width) { | ||
if (justify === undefined || justify === 'left') { | ||
result += ' '.repeat(width - result.length) | ||
if (width !== undefined && width !== 0) { | ||
if (result.length >= width) { | ||
result = result.substring(0, width) | ||
} | ||
else if (justify === 'right') { | ||
result = ' '.repeat(width - result.length) + result | ||
} | ||
else { | ||
// justify === center | ||
const before = Math.floor((width - result.length) / 2) | ||
result = ' '.repeat(before) + result + ' '.repeat(width - result.length - before) | ||
if (justify === undefined || justify === 'left') { | ||
result += ' '.repeat(width - result.length) | ||
} | ||
else if (justify === 'right') { | ||
result = ' '.repeat(width - result.length) + result | ||
} | ||
else { | ||
// justify === center | ||
const before = Math.floor((width - result.length) / 2) | ||
result = ' '.repeat(before) + result + ' '.repeat(width - result.length - before) | ||
} | ||
} | ||
@@ -225,2 +263,3 @@ } | ||
width: number = 0 | ||
actualWidth: number = 0 | ||
constructor(name: string) { | ||
@@ -227,0 +266,0 @@ this.name = name |
@@ -11,2 +11,3 @@ #!/usr/bin/env node | ||
import { InitCommand } from './InitCommand' | ||
import { AssetCommand } from './AssetCommand' | ||
const logger = loglevel.getLogger("index.ts") | ||
@@ -20,2 +21,4 @@ logger.setLevel("debug") | ||
program.addCommand(new InitCommand('init')) | ||
program.addCommand(new AssetCommand('asset', 'manage assets')) | ||
program.addCommand(new AssetCommand('a', 'manage assets')) | ||
program.addCommand(new ModuleCommand('module')) | ||
@@ -22,0 +25,0 @@ program.addCommand(new ModuleCommand('m')) |
@@ -34,5 +34,7 @@ #!/usr/bin/env ts-node | ||
function getModuleDBName(filePath: string): string { | ||
let result | ||
if (filePath.endsWith('.xml') === false) throw filePath + ' does not have .xml extension' | ||
result = path.basename(filePath, '.xml') | ||
// let result | ||
// if (filePath.endsWith('.xml') === false) throw filePath + ' does not have .xml extension' | ||
// result = path.basename(filePath, '.xml') | ||
const result = path.parse(filePath).name | ||
const identifierRegex = /^[_a-zA-Z][_a-zA-Z0-9]*$/ | ||
@@ -50,3 +52,3 @@ if (result.match(identifierRegex) === null) throw 'filename ' + result + ' does not match identifier syntax' | ||
this.description(description) | ||
.option('-i --id <id>', 'module ID to download') | ||
.option('-i --id <id>', 'module ID to upload') | ||
.argument('[files...]', 'local module file(s) to upload to the server') | ||
@@ -161,3 +163,3 @@ .action(async (files: any, options: any) => { | ||
if (fs.existsSync(filePath) === true) { | ||
logger.info('file ' + filePath + ' already exists and will not be overwritten ') | ||
logger.info('file ' + filePath + ' already exists. Remove it before launching the command if you want to overwrite.') | ||
} | ||
@@ -164,0 +166,0 @@ else { |
#!/usr/bin/env ts-node | ||
import { DBManager } from "@cyklang/core" | ||
import { DBManager, DBTable } from "@cyklang/core" | ||
import * as fs from "fs" | ||
@@ -8,2 +8,6 @@ import loglevel from 'loglevel' | ||
import { Cmd } from "./Cmd" | ||
import { DBClient } from "./DBClient" | ||
import { Stream } from "form-data" | ||
import FormData from 'form-data' | ||
const logger = loglevel.getLogger("TableCommand.ts") | ||
@@ -17,2 +21,7 @@ logger.setLevel("debug") | ||
this.addCommand(new TableExportCmd()) | ||
this.addCommand(new TableImportCmd()) | ||
this.addCommand(new TableList('list', 'list database tables')) | ||
this.addCommand(new TableList('l', '(l)ist database tables')) | ||
this.addCommand(new TableQuery('query', 'query select table content')) | ||
this.addCommand(new TableQuery('q', 'query select table content')) | ||
} | ||
@@ -22,32 +31,145 @@ } | ||
class TableExportCmd extends Cmd { | ||
constructor() { | ||
super('export') | ||
this.description('export to CSV file(s)') | ||
.argument('<tables...>', 'table(s) to export') | ||
.option('-f --file <file>', 'name of the file to create, defaults to <table_name>.csv') | ||
.option('-d --dir <directory>', 'name of the directory where to create exported files') | ||
.action(async (tables, options) => this.commandExport( tables, options)) | ||
.argument('<tables...>', 'table(s) to export') | ||
.option('-f --file <file>', 'name of the file to create, defaults to <table_name>.csv') | ||
.option('-d --dir <directory>', 'name of the directory where to create exported files') | ||
.action(async (tables, options) => this.commandExport(tables, options)) | ||
} | ||
async commandExport(tables: string[], options: Options) { | ||
await this.prologue(options) | ||
if (this.dbManager === undefined) throw 'dbManager undefined' | ||
await commandImportExport(this.dbManager, 'to', tables, options) | ||
// await commandImportExport(this.dbManager, 'to', tables, options) | ||
try { | ||
if (options.file === undefined && options.dir === undefined) throw '--file or --dir mandatory' | ||
if (options.file !== undefined && options.dir !== undefined) throw '--file and --dir are mutually exclusive' | ||
if (tables.length === 0) throw 'at least one table name is mandatory' | ||
if (tables.length > 1 && options.dir === undefined) throw 'for multiple tables, --dir is mandatory' | ||
for (let indi = 0; indi < tables.length; indi++) { | ||
const tableName = tables[indi] | ||
const dbTable = await this.dbManager.dbTableExist(tableName) | ||
if (dbTable === undefined) { | ||
logger.info('table ' + tableName + ' not found') | ||
continue | ||
} | ||
const filename = buildFilename(dbTable.name, options) | ||
await this.copyTo(dbTable, filename) | ||
} | ||
} | ||
catch (err) { | ||
logger.error(err) | ||
} | ||
} | ||
async copyTo(dbTable: DBTable, filename: string) { | ||
if (this.dbRemote === undefined) throw 'dbRemote undefined' | ||
try { | ||
const writeStream = fs.createWriteStream(filename) | ||
const response = await this.dbRemote.apiServer.get('/admin/export/' + dbTable.name, { responseType: 'stream' }) | ||
const stream = response as Stream | ||
stream.on('data', (data: Uint8Array) => { | ||
logger.debug('**** DATA ****', data.length) | ||
const textDecoder = new TextDecoder() | ||
const chunk = (textDecoder.decode(data)) | ||
writeStream.write(chunk) | ||
}) | ||
stream.on('end', () => logger.debug('**** DONE ****')) | ||
} | ||
catch (err) { | ||
logger.error(err) | ||
} | ||
} | ||
} | ||
class TableImportCmd extends Cmd { | ||
constructor() { | ||
super('import') | ||
this.description('import from CSV file(s)') | ||
.option('-f --file <file>', 'name of the file to import, defaults to <table_name>.csv') | ||
.option('-d --dir <directory>', 'name of the directory where to find files to import') | ||
.action(async (tables, options) => this.commandImport(tables, options)) | ||
.argument('<tables...>', 'table(s) to import') | ||
.option('-f --file <file>', 'name of the file to import, defaults to <table_name>.csv') | ||
.option('-d --dir <directory>', 'name of the directory where to find files to import') | ||
.action(async (tables, options) => this.commandImport(tables, options)) | ||
} | ||
async commandImport(tables: string[], options: Options) { | ||
await this.prologue(options) | ||
if (this.dbManager === undefined) throw 'dbManager undefined' | ||
await commandImportExport(this.dbManager, 'from', tables, options) | ||
// await commandImportExport(this.dbManager, 'to', tables, options) | ||
try { | ||
if (options.file === undefined && options.dir === undefined) throw '--file or --dir mandatory' | ||
if (options.file !== undefined && options.dir !== undefined) throw '--file and --dir are mutually exclusive' | ||
if (tables.length === 0) throw 'at least one table name is mandatory' | ||
if (tables.length > 1 && options.dir === undefined) throw 'for multiple tables, --dir is mandatory' | ||
for (let indi = 0; indi < tables.length; indi++) { | ||
const tableName = tables[indi] | ||
const dbTable = await this.dbManager.dbTableExist(tableName) | ||
if (dbTable === undefined) { | ||
logger.info('table ' + tableName + ' not found') | ||
continue | ||
} | ||
const filename = buildFilename(dbTable.name, options) | ||
this.copyFrom(dbTable, filename) | ||
} | ||
} | ||
catch (err) { | ||
logger.error(err) | ||
} | ||
} | ||
async copyFrom(dbTable: DBTable, filename: string) { | ||
if (this.dbRemote === undefined) throw 'dbRemote undefined' | ||
try { | ||
const file = fs.createReadStream(filename) | ||
const form = new FormData() | ||
form.append('uploadFile', file) | ||
const route = '/admin/import/' + dbTable.name | ||
const resp = await this.dbRemote.apiServer.post(route, form) | ||
if (resp.status === 200) { | ||
logger.debug(filename + ' uploaded') | ||
} | ||
} | ||
catch (err) { | ||
logger.error(err) | ||
} | ||
} | ||
} | ||
function buildFilename(tableName: string, options: Options): string { | ||
let filename | ||
if (options.file !== undefined) { | ||
if (fs.existsSync(options.file) && fs.lstatSync(options.file).isDirectory()) | ||
throw '--file ' + options.file + ' is a directory' | ||
filename = options.file | ||
} | ||
else if (options.dir === undefined) | ||
throw '--file and --dir unspecified' | ||
else if (fs.existsSync(options.dir) === false) | ||
throw 'directory ' + options.dir + ' does not exist' | ||
else if (fs.lstatSync(options.dir).isFile()) { | ||
throw ("--dir " + options.dir + " is a file ") | ||
} | ||
else { | ||
let directory = options.dir.trim() | ||
if (directory[directory.length - 1] !== '/') { | ||
directory += '/' | ||
} | ||
filename = directory + tableName + '.csv' | ||
} | ||
return filename | ||
} | ||
interface Options { | ||
@@ -75,57 +197,148 @@ env: string | undefined | ||
} | ||
let lcols: string | undefined | ||
for (let indj = 0; indj < dbTable.columns.length; indj++) { | ||
const dbColumn = dbTable.columns[indj] | ||
if (lcols === undefined) { | ||
lcols = "(" | ||
} | ||
else { | ||
lcols += "," | ||
} | ||
lcols += dbColumn.name | ||
} | ||
lcols += ")" | ||
psqlCmd(dbTable, direction, options) | ||
} | ||
} | ||
catch (err) { | ||
logger.error(err) | ||
} | ||
} | ||
let psql_cmd = "psql -c" | ||
if (process.env.HEROKU_CLI !== undefined) { | ||
psql_cmd = process.env.HEROKU_CLI + ' -c ' | ||
} | ||
let filename | ||
if (options.file !== undefined) { | ||
if (fs.existsSync(options.file) && fs.lstatSync(options.file).isDirectory()) | ||
throw '--file ' + options.file + ' is a directory' | ||
filename = options.file | ||
} | ||
else if (options.dir === undefined) | ||
throw '--file and --dir unspecified' | ||
else if (fs.existsSync(options.dir) === false) | ||
throw 'directory ' + options.dir + ' does not exist' | ||
else if (fs.lstatSync(options.dir).isFile()) { | ||
logger.debug("--dir " + options.dir + " is a file ") | ||
} | ||
else { | ||
let directory = options.dir.trim() | ||
if (directory[directory.length - 1] !== '/') { | ||
directory += '/' | ||
} | ||
filename = directory + tableName + '.csv' | ||
} | ||
//-------------------------------------------------------------------------------------------------------------------- | ||
// function psqlCmd | ||
// invoke local install of psql CLI | ||
//-------------------------------------------------------------------------------------------------------------------- | ||
let copy_cmd = "\\copy " + tableName + lcols + direction + " '" + filename + "' csv header" | ||
const shell_cmd = psql_cmd + '"' + copy_cmd + '"' | ||
logger.debug(shell_cmd) | ||
const child = spawn(shell_cmd.trim(), { stdio: 'inherit', shell: true }) | ||
// exec(shell_cmd.trim(), (error, stdout, stderr) => { | ||
// if (error) throw error | ||
// logger.debug(stdout, stderr) | ||
// }) | ||
function psqlCmd(dbTable: DBTable, direction: 'from' | 'to', options: Options,) { | ||
let lcols: string | undefined | ||
for (let indj = 0; indj < dbTable.columns.length; indj++) { | ||
const dbColumn = dbTable.columns[indj] | ||
if (lcols === undefined) { | ||
lcols = "(" | ||
} | ||
else { | ||
lcols += "," | ||
} | ||
lcols += dbColumn.name | ||
} | ||
catch (err) { | ||
logger.error(err) | ||
lcols += ")" | ||
let psql_cmd = "psql -c" | ||
if (process.env.HEROKU_CLI !== undefined) { | ||
psql_cmd = process.env.HEROKU_CLI + ' -c ' | ||
} | ||
let filename | ||
if (options.file !== undefined) { | ||
if (fs.existsSync(options.file) && fs.lstatSync(options.file).isDirectory()) | ||
throw '--file ' + options.file + ' is a directory' | ||
filename = options.file | ||
} | ||
else if (options.dir === undefined) | ||
throw '--file and --dir unspecified' | ||
else if (fs.existsSync(options.dir) === false) | ||
throw 'directory ' + options.dir + ' does not exist' | ||
else if (fs.lstatSync(options.dir).isFile()) { | ||
logger.debug("--dir " + options.dir + " is a file ") | ||
} | ||
else { | ||
let directory = options.dir.trim() | ||
if (directory[directory.length - 1] !== '/') { | ||
directory += '/' | ||
} | ||
filename = directory + dbTable.name + '.csv' | ||
} | ||
let copy_cmd = "\\copy " + dbTable.name + lcols + direction + " '" + filename + "' csv header" | ||
const shell_cmd = psql_cmd + '"' + copy_cmd + '"' | ||
logger.debug(shell_cmd) | ||
const child = spawn(shell_cmd.trim(), { stdio: 'inherit', shell: true }) | ||
} | ||
//-------------------------------------------------------------------------------------------------------------------- | ||
// class TableList | ||
//-------------------------------------------------------------------------------------------------------------------- | ||
class TableList extends Cmd { | ||
constructor(name: string, description: string) { | ||
super(name) | ||
this.description(description) | ||
.option('-s --sort <columns>', 'sort list by column numbers (begins with 0) separated by comma') | ||
.action(async (options: any) => { | ||
await this.commandList(options) | ||
}) | ||
} | ||
async commandList(options: any) { | ||
await this.prologue(options) | ||
if (this.dbManager === undefined) throw 'dbManager undefined' | ||
const dbClient = new DBClient(this.dbManager) | ||
dbClient.selectFromTable('List of Tables', 'cyk_table', | ||
{ fields: 'table_id,table_name,table_description,table_access', sort: options.sort || '1' } | ||
) | ||
} | ||
} | ||
//-------------------------------------------------------------------------------------------------------------------- | ||
// class TableQuery | ||
//-------------------------------------------------------------------------------------------------------------------- | ||
class TableQuery extends Cmd { | ||
constructor(name: string, description: string) { | ||
super(name) | ||
this.description(description) | ||
.argument('<table>', 'table to query') | ||
.option('--where <clause_where_sql>', 'criteria in SQL syntax') | ||
.option('-s --sort <columns>', 'sort by columns positions (begins by 0) separated by comma') | ||
.option('-w --width <colwidth>', 'columns widths separated by comma') | ||
.action(async (table, options) => { | ||
this.commandQuery(table, options) | ||
}) | ||
} | ||
async commandQuery(table: string, options: any) { | ||
try { | ||
await this.prologue(options) | ||
if (this.dbManager === undefined) throw 'dbManager undefined' | ||
const dbTable = await this.dbManager.dbTableExist(table) | ||
if (dbTable === undefined) throw 'table ' + table + ' not found' | ||
let fields = '' | ||
for (let ind = 0; ind < dbTable.columns.length; ind++) { | ||
const dbColumn = dbTable.columns[ind] | ||
let ok = false | ||
let fieldName = dbColumn.name | ||
if (dbColumn.dbType === 'text' || dbColumn.dbType === 'bytea') { | ||
ok = false | ||
fieldName = 'length(' + dbColumn.name + ')' | ||
} | ||
else { | ||
ok = true | ||
} | ||
if (ok === true) { | ||
if (fields !== '') fields += ',' | ||
fields += fieldName | ||
} | ||
} | ||
logger.debug('commandQuery', table) | ||
const dbClient = new DBClient(this.dbManager) | ||
let title = 'Table ' + table | ||
if (options.where !== undefined) { | ||
title += ' where ' + options.where | ||
} | ||
dbClient.selectFromTable(title, table, | ||
{ fields: fields, width: options.width, sort: options.sort || '1', where: options.where }) | ||
} | ||
catch (err) { | ||
logger.error(err) | ||
} | ||
} | ||
} | ||
279686
102
4444
8
7
51
+ Addedform-data@^4.0.0
+ Addedmime-types@^2.1.35