Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: csv #90

Merged
merged 5 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ on:
push:
branches:
- master
pull_request:
branches:
- master

jobs:
test:
Expand Down
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,17 @@
</a>
</p>

| Branch | Tests | Coverage |
|--------------|-----------------|----------------|
| Branch | Tests | Coverage |
| -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| `master` | ![Tests](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/AElfProject/aelf-command/feature/badge-json/master-test-results.json) | ![Coverage](https://AElfProject.github.io/aelf-command/badges.svg) |




## Descriptions

_A CLI tools built for AElf_

## Features

- Get or Set common configs, `endpoint`, `account`, `datadir`, `password`.
- Get or Set common configs, `endpoint`, `account`, `datadir`, `password`, `csv`.
- For new users who are not familiar with the CLI parameters, any missing parameters will be asked in a prompting way.
- Create a new `account`.
- Load an account from a given `private key` or `mnemonic`.
Expand Down Expand Up @@ -157,6 +154,7 @@ Options:
-a, --account <account> The address of AElf wallet
-p, --password <password> The password of encrypted keyStore
-d, --datadir <directory> The directory that contains the AElf related files. Defaults to {home}/.local/share/aelf
-c, --csv <csv> The location of the CSV file containing the parameters.
-h, --help output usage information

Commands:
Expand Down Expand Up @@ -218,6 +216,7 @@ aelf-command console
- `endpoint`: The endpoint for the RPC service.
- `account`: The account to be used to interact with the blockchain `endpoint`.
- `password`: The password for unlocking the given `account`.
- `csv>`: The location of the CSV file containing the parameters.

You can specified options above in several ways, and the priority is in the order of low to high.

Expand Down
4 changes: 2 additions & 2 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default {
coveragePathIgnorePatterns: ['/node_modules/', '/src/utils/constants.js', '/src/command/index.js'],

// A list of reporter names that Jest uses when writing coverage reports
coverageReporters: ['text', 'json-summary'],
coverageReporters: ['text', 'json-summary', 'html'],

// An object that configures minimum threshold enforcement for coverage results
// coverageThreshold: null,
Expand Down Expand Up @@ -146,7 +146,7 @@ export default {

// The glob patterns Jest uses to detect test files
testMatch: [
// '**/test/utils/Logger.test.js'
// '**/test/utils/utils.test.js'
'**/test/command/dappServer/socket-sign.test.js',
'**/test/**/?(*.)+(spec|test).[jt]s?(x)'
// "**/?(*.)+(spec|test).[tj]s?(x)"
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "aelf-command",
"version": "0.1.48",
"version": "0.1.49-beta.0",
"description": "A CLI tools for AElf",
"main": "src/index.js",
"type": "module",
Expand Down Expand Up @@ -56,6 +56,7 @@
"check-node-version": "^4.2.1",
"columnify": "^1.6.0",
"commander": "^12.1.0",
"csv-parser": "^3.0.0",
"elliptic": "^6.5.5",
"inquirer": "^9.2.22",
"inquirer-date-prompt": "^3.0.0",
Expand Down
4 changes: 2 additions & 2 deletions src/command/baseSubCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import inquirer from 'inquirer';
import ora from 'ora';
import { logger } from '../utils/myLogger.js';
import { camelCase } from '../utils/utils.js';
import { globalOptionsPrompts, strictGlobalOptionValidatorDesc } from '../utils/constants.js';
import { commonGlobalOptionValidatorDesc, globalOptionsPrompts, strictGlobalOptionValidatorDesc } from '../utils/constants.js';

// Schema.warning = () => {}; // TypeError: Cannot add property warning, object is not extensible

Expand Down Expand Up @@ -123,7 +123,7 @@ class BaseSubCommand {
*/
static getUniConfig(commander) {
const result = {};
['password', 'endpoint', 'account', 'datadir'].forEach(v => {
Object.keys(commonGlobalOptionValidatorDesc).forEach(v => {
const options = commander.opts();
if (options[v]) {
result[v] = options[v];
Expand Down
53 changes: 40 additions & 13 deletions src/command/call.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import AElf from 'aelf-sdk';
import inquirer from 'inquirer';
import chalk from 'chalk';
import { createReadStream } from 'fs';
import csv from 'csv-parser';
import BaseSubCommand from './baseSubCommand.js';
import { callCommandUsages, callCommandParameters } from '../utils/constants.js';
import {
Expand All @@ -9,7 +11,8 @@ import {
getMethod,
promptTolerateSeveralTimes,
getParams,
parseJSON
parseJSON,
parseCSV
} from '../utils/utils.js';
import { getWallet } from '../utils/wallet.js';
import { logger } from '../utils/myLogger.js';
Expand Down Expand Up @@ -64,6 +67,19 @@ class CallCommand extends BaseSubCommand {
return contractAddress;
}

/**
* Calls a method with specified parameters.
* @param {any} method The method to call.
* @param {any} params The parameters for the method call.
* @returns {Promise<any>} A promise that resolves with the result of the method call.
*/
async showRes(method, params) {
const result = await this.callMethod(method, params);
// @ts-ignore
logger.info(`\nResult:\n${JSON.stringify(result, null, 2)}`);
this.oraInstance.succeed('Succeed!');
}

/**
* Runs the command.
* @param {Command} commander The Commander instance.
Expand All @@ -75,7 +91,7 @@ class CallCommand extends BaseSubCommand {
// @ts-ignore
const { options, subOptions } = await super.run(commander, ...args);
const subOptionsLength = Object.keys(subOptions).length;
const { endpoint, datadir, account, password } = options;
const { endpoint, datadir, account, password, csv } = options;
const aelf = new AElf(new AElf.providers.HttpProvider(endpoint));
try {
let { contractAddress, method, params } = subOptions;
Expand Down Expand Up @@ -109,13 +125,16 @@ class CallCommand extends BaseSubCommand {
break;
case 'params':
contractAddress = await getContractInstance(contractAddress, aelf, wallet, this.oraInstance);

method = getMethod(method, contractAddress);

params = await getParams(method);
params = typeof params === 'string' ? params : BaseSubCommand.normalizeConfig(params);
if (Object.keys(params || {}).length > 0) {
console.log(chalk.hex('#3753d3')(`The params you entered is:\n${JSON.stringify(params, null, 2)}`));
if (csv) {
const csvParams = await parseCSV(csv);
params = csvParams;
} else {
params = await getParams(method);
params = typeof params === 'string' ? params : BaseSubCommand.normalizeConfig(params);
if (Object.keys(params || {}).length > 0) {
console.log(chalk.hex('#3753d3')(`The params you entered is:\n${JSON.stringify(params, null, 2)}`));
}
}
break;
default:
Expand All @@ -124,15 +143,23 @@ class CallCommand extends BaseSubCommand {
}
}
contractAddress = await getContractInstance(contractAddress, aelf, wallet, this.oraInstance);
params = parseJSON(params);
if (Array.isArray(params)) {
params.forEach(param => parseJSON(param));
} else {
params = parseJSON(params);
}

method = getMethod(method, contractAddress);
if (method.inputTypeInfo && (Object.keys(method.inputTypeInfo.fields).length === 0 || !method.inputTypeInfo.fields)) {
params = '';
}
const result = await this.callMethod(method, params);
// @ts-ignore
logger.info(`\nResult:\n${JSON.stringify(result, null, 2)}`);
this.oraInstance.succeed('Succeed!');
if (Array.isArray(params)) {
for (const param of params) {
await this.showRes(method, param);
}
} else {
await this.showRes(method, params);
}
} catch (e) {
this.oraInstance.fail('Failed!');
// @ts-ignore
Expand Down
2 changes: 1 addition & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ function init(options) {
commander.option('-e, --endpoint <URI>', 'The URI of an AElf node. Eg: http://127.0.0.1:8000');
commander.option('-a, --account <account>', 'The address of AElf wallet');
commander.option('-p, --password <password>', 'The password of encrypted keyStore');

commander.option(
'-d, --datadir <directory>',
`The directory that contains the AElf related files. Default to be ${userHomeDir}/aelf`
);
commander.option('-c, --csv <csv>', 'The location of the CSV file containing the parameters.');
const rc = new RC();
Object.values(commands).forEach(Value => {
const command = new Value(rc);
Expand Down
9 changes: 7 additions & 2 deletions src/utils/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,11 @@ const commonGlobalOptionValidatorDesc = {
type: 'string',
required: false,
message: 'set a valid account address in global config file or passed by -a <address>'
},
csv: {
type: 'string',
required: false,
message: 'set params in csv file by -c <csv>'
}
};

Expand All @@ -374,8 +379,8 @@ const strictGlobalOptionValidatorDesc = /**@type {CommonGlobalOptionValidatorDes
// @ts-ignore
Object.entries(commonGlobalOptionValidatorDesc).forEach((/** @type {[CommonGlobalOptionKey, any]} */ [key, value]) => {
strictGlobalOptionValidatorDesc[key] = {
...value,
required: true
...value
// required: true
};
});

Expand Down
22 changes: 21 additions & 1 deletion src/utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import _camelCase from 'camelcase';
import inquirer from 'inquirer';
import { plainLogger } from './myLogger.js';
import protobuf from '@aelfqueen/protobufjs';
import { createReadStream } from 'fs';
import csv from 'csv-parser';
const { load } = protobuf;

/**
Expand Down Expand Up @@ -462,6 +464,23 @@ async function deserializeLogs(aelf, logs = []) {
return results;
}

const parseCSV = async address => {
let results = [];
const stream = createReadStream(address).pipe(csv());
for await (const data of stream) {
const cleanData = {};
for (const key in data) {
const cleanKey = key.replace(/\n/g, '').trim();
const cleanValue = typeof data[key] === 'string' ? data[key].replace(/\n/g, '').trim() : data[key];
if (cleanValue !== '') {
cleanData[cleanKey] = cleanValue;
}
}
Object.keys(cleanData).length && results.push(cleanData);
}
return results;
};

export {
promisify,
camelCase,
Expand All @@ -474,5 +493,6 @@ export {
parseJSON,
randomId,
getParams,
deserializeLogs
deserializeLogs,
parseCSV
};
22 changes: 21 additions & 1 deletion test/command/call.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { callCommandUsages, callCommandParameters } from '../../src/utils/consta
import { getContractInstance } from '../../src/utils/utils.js';
import { userHomeDir } from '../../src/utils/userHomeDir.js';
import { logger } from '../../src/utils/myLogger.js';
import { endpoint as endPoint, account, password, dataDir } from '../constants.js';
import { endpoint as endPoint, account, password, dataDir, csvDir } from '../constants.js';

const sampleRc = { getConfigs: jest.fn() };
jest.mock('../../src/utils/myLogger');
Expand Down Expand Up @@ -102,6 +102,26 @@ describe('CallCommand', () => {
expect(logger.info).toHaveBeenCalled();
});

test('should run with csv', async () => {
inquirer.prompt = questions =>
Promise.resolve({
symbol: 'ELF',
owner: 'GyQX6t18kpwaD9XHXe1ToKxfov8mSeTLE9q9NwUAeTE8tULZk'
});
const commander = new Command();
commander.option('-e, --endpoint <URI>', 'The URI of an AElf node. Eg: http://127.0.0.1:8000');
commander.option('-a, --account <account>', 'The address of AElf wallet');
commander.option('-p, --password <password>', 'The password of encrypted keyStore');
commander.option(
'-d, --datadir <directory>',
`The directory that contains the AElf related files. Default to be ${userHomeDir}/aelf`
);
commander.option('-c, --csv <csv>', 'The location of the CSV file containing the parameters.');
commander.parse([process.argv[0], '', 'call', '-e', endPoint, '-a', account, '-p', password, '-d', dataDir, '-c', csvDir]);
await callCommand.run(commander, 'AElf.ContractNames.Token', 'GetBalance');
expect(logger.info).toHaveBeenCalled();
});

test('should run with invalid parameters', async () => {
inquirer.prompt = backup;
callCommand = new CallCommand(sampleRc, 'call', 'Call a read-only method on a contract.', [
Expand Down
2 changes: 0 additions & 2 deletions test/command/proposal.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
/* eslint-disable max-len */
import { Command } from 'commander';
import path from 'path';
import inquirer from 'inquirer';
import AElf from 'aelf-sdk';
import chalk from 'chalk';
import moment from 'moment';
import ProposalCommand from '../../src/command/proposal.js';
import { userHomeDir } from '../../src/utils/userHomeDir.js';
Expand Down
1 change: 1 addition & 0 deletions test/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export const endpoint = 'https://tdvw-test-node.aelf.io/';
export const account = 'GyQX6t18kpwaD9XHXe1ToKxfov8mSeTLE9q9NwUAeTE8tULZk';
export const password = '1234*Qwer';
export const dataDir = path.resolve(__dirname, './dataDir/aelf');
export const csvDir = path.resolve(__dirname, './test.csv');
3 changes: 3 additions & 0 deletions test/test.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
symbol,"owner
"
ELF,GyQX6t18kpwaD9XHXe1ToKxfov8mSeTLE9q9NwUAeTE8tULZk
12 changes: 10 additions & 2 deletions test/utils/utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ import {
parseJSON,
randomId,
getParams,
deserializeLogs
deserializeLogs,
parseCSV
} from '../../src/utils/utils';
import { plainLogger } from '../../src/utils/myLogger';
import { endpoint, account, password, dataDir } from '../constants.js';
import { endpoint, account, password, dataDir, csvDir } from '../constants.js';

jest.mock('inquirer');

Expand Down Expand Up @@ -391,4 +392,11 @@ describe('utils', () => {
expect(result).toEqual(null);
});
});

describe('parseCSV', () => {
test('test parse csv file', async () => {
const results = await parseCSV(csvDir);
expect(results).toEqual([{ owner: 'GyQX6t18kpwaD9XHXe1ToKxfov8mSeTLE9q9NwUAeTE8tULZk', symbol: 'ELF' }]);
});
});
});
6 changes: 6 additions & 0 deletions types/utils/constants.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,17 @@ export interface AccountValidatorDesc {
required: boolean;
message: string;
}
export interface CSVValidatorDesc {
type: string;
required: boolean;
message: string;
}
export interface CommonGlobalOptionValidatorDesc {
password: PasswordValidatorDesc;
endpoint: EndpointValidatorDesc;
datadir: DatadirValidatorDesc;
account: AccountValidatorDesc;
csv: CSVValidatorDesc;
}
export const commonGlobalOptionValidatorDesc: CommonGlobalOptionValidatorDesc;

Expand Down
7 changes: 7 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3085,6 +3085,13 @@ crypto-random-string@^4.0.0:
dependencies:
type-fest "^1.0.1"

csv-parser@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/csv-parser/-/csv-parser-3.0.0.tgz#b88a6256d79e090a97a1b56451f9327b01d710e7"
integrity sha512-s6OYSXAK3IdKqYO33y09jhypG/bSDHPuyCme/IdEHfWpLf/jKcpitVFyOC6UemgGk8v7Q5u2XE0vvwmanxhGlQ==
dependencies:
minimist "^1.2.0"

[email protected], cz-conventional-changelog@^3.3.0:
version "3.3.0"
resolved "https://registry.npmjs.org/cz-conventional-changelog/-/cz-conventional-changelog-3.3.0.tgz#9246947c90404149b3fe2cf7ee91acad3b7d22d2"
Expand Down
Loading